简体   繁体   中英

Generically accessing multidimensional arrays in C#

C# allows creating and populating multidimensional arrays, here is a simple example:

    public static void Main(String[] args)
    {
        var arr = (int[,])CreateArray(new [] {2, 3}, 8);
        Console.WriteLine("Value: " + arr[0,0]);
    }

    // Creates a multidimensional array with the given dimensions, and assigns the
    // given x to the first array element
    public static Array CreateArray<T>(int[] dimLengths, T x)
    {
        var arr = Array.CreateInstance(typeof(T), dimLengths);
        var indices = new int[dimLengths.Length];
        for (var i = 0; i < indices.Length; i++)
            indices[i] = 0;
        arr.SetValue(x, indices);  // Does boxing/unboxing
        return arr;
    }

This works well. However, for some reason there is no generic version of Array.SetValue(), so the code above does boxing/unboxing, which I'd like to avoid. I was wondering if I missed something or if this is an omission in the .NET API?

No, you are not missing anything: Arrays does not have an option that sets the value without boxing and unboxing.

You do have an alternative to this with LINQ, but it is probably going to be slower than boxing/unboxing for a single element, because compiling a dynamic lambda would "eat up" the potential benefits:

public static Array CreateArray<T>(int[] dimLengths, T x) {
    var arr = Array.CreateInstance(typeof(T), dimLengths);

    var p = Expression.Parameter(typeof(object), "arr");
    var ind = new Expression[dimLengths.Length];
    for (var i = 0; i < dimLengths.Length; i++) {
        ind[i] = Expression.Constant(0);
    }
    var v = Expression.Variable(arr.GetType(), "cast");
    var block = Expression.Block(
        new[] {v}
    ,   new Expression[] {
            Expression.Assign(v, Expression.Convert(p, arr.GetType()))
        ,   Expression.Assign(Expression.ArrayAccess(v, ind), Expression.Constant(x))
        ,   Expression.Constant(null, typeof(object))
        }
    );
    Expression.Lambda<Func<object, object>>(block, p).Compile()(arr);
    return arr;
}

If you wanted to set all elements in a loop, you could modify the above to compile a dynamically created lambda with multiple nested loops. In this case, you could get an improvement on having to perform multiple boxing and unboxing in a series of nested loops.

for some reason there is no generic version of Array.SetValue()

While it is definitely possible to write a generic method similar to SetValue in the Array class, it may not be desirable. A generic method on a non-generic class would give a false promise of compile-time type safety, which cannot be guaranteed, because the compiler does not know the runtime type of the Array object.

I didn't find any generic ways either to set a value into an Array instance, so I guess the only workaround is to use the unsafe context to avoid boxing.

However, there can be no generic version , now when I think of it. See, when you define a generic method method<T>()... , you do define the parameter for the method: ...<T>(T[] a)... where you have to be specific about the dimensions count, which is one . To create a twodimensional parameter, you define it like this ...<T>(T[,] a)... and so on.

As you can see, by the current syntax of C#, you simple cannot create a generic method, which can accept any-dimensional array.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM