Expression getting length of an array

arrays c# cil expression-trees

Question

If you have an Expression of Type Array but not of a specific array type such as int[], how to I generate an expression which gets the length in a fast way without doing the whole property fetching malarkay.

E.g. in the following

ParameterExpression para3 = Expression.Parameter(typeof(int[]), "p3");
ParameterExpression para4 = Expression.Parameter(typeof(Array), "p4");

Type pt1 = para3.Type.GetElementType();
Type pt2 = para4.Type.GetElementType();

MethodInfo mArrayLength = Strong.Instance<Array>.Property<int>(a => a.Length).GetGetMethod();

Expression asdf5 = Expression.ArrayLength(para3);
Expression asdf6 = Expression.ArrayLength(para4);
Expression asdf7 = Expression.Call(para4, mArrayLength);

mArrayLength is just the get method for the Length property on type Array.

Here the expression asdf5 works since para5 is of type int[] but asdf6 does not because para6's type is only of type Array. asdf7 does work.

What I want is to effectively use the ldlen instruction, which only requires an Object, rather than calling a method. Is this just a limitation with the expression tree library?

You can edit the field using reflection, and you can even compile the expression! But trying to run the delegate will result in an operation could destabilize the runtime exception.

Array parr = new int[5];

Expression pArraylength = Expression.ArrayLength(para3);
pOperandFieldInfo.SetValue(pArraylength, para4);

Expression<Func<Array, int>> pexchanger = (Expression<Func<Array, int>>)Expression.Lambda(pArraylength, para4);
Func<Array, int> pFunc = pexchanger.Compile();
int pint = pFunc(parr);
1
1
5/21/2018 8:09:40 PM

Accepted Answer

There are two types of arrays in .NET, the mono-dimensional, zero-based (the first index is 0) arrays (int[] for example) that are supported directly by the IL language (and by the Expression class) (as a side note they are called SZ arrays), and the other arrays (multi-dimensional ones e.g. int[5,3] and arrays with first index different from that exist for compatibility with some languages that support them, for example the old VB), that don't have a direct support in the IL language but are supported by the compiler that uses calls to the Array class. They are based on two different classes that are both subclasses of Array. As I've written, the low-level IL instructions are only for the mono-dimensional, zero-based ones. An Array object could be both, so there is no support from Expression.ArrayLength.

You can easily see this in SharpLab:

public static int A1<T>(T[] array) {
    return array.Length;
}

public static int A2(Array array) {
    return array.Length;
}

.method public hidebysig static 
    int32 A1<T> (
        !!T[] 'array'
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 4 (0x4)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: ldlen
    IL_0002: conv.i4
    IL_0003: ret
} // end of method C::A1

.method public hidebysig static 
    int32 A2 (
        class [mscorlib]System.Array 'array'
    ) cil managed 
{
    // Method begins at RVA 0x2055
    // Code size 7 (0x7)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: callvirt instance int32 [mscorlib]System.Array::get_Length()
    IL_0006: ret
} // end of method C::A2

Just out of curiousity, using an undocumented feature (we are playing with fire here!)...

ParameterExpression par = Expression.Parameter(typeof(Array), "array");
var conv = Expression.New(typeof(Conv));
var init = Expression.MemberInit(conv, Expression.Bind(typeof(Conv).GetField(nameof(Conv.Array)), par));
var array = Expression.Field(init, nameof(Conv.Array2));
var length = Expression.ArrayLength(array);
var lambda = Expression.Lambda<Func<Array, int>>(length, par);
var compiled = lambda.Compile();

where Conv is:

[StructLayout(LayoutKind.Explicit)]
public struct Conv
{
    [FieldOffset(0)]
    public Array Array;
    [FieldOffset(0)]
    public byte[] Array2;
}

Use it like:

int length = compiled(new int[100]);

The "trick" here is that through FieldOffest we can cast incompatible types that have the same memory layout. All the sz arrays share the same header format (where the Length is contained), so we "convert" our Array to an byte[] array (note that we could have probably converted it to any other type, but there is a distinct advantage in a byte: it is the smallest available type, so we are sure that we can't go "out" of the array in any way).

The expression tree is something like:

static int GetLength(Array array)
{
    return new Conv { Array = array }.Array2.Length;
}
5
5/22/2018 9:01:56 PM


Related Questions





Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow