代码之家  ›  专栏  ›  技术社区  ›  Nick

获取数组长度的表达式

  •  1
  • Nick  · 技术社区  · 6 年前

    如果您有一个类型为array但不是特定数组类型(如int[])的表达式,那么如何生成一个快速获取长度的表达式,而不需要执行整个属性获取malarkay。

    例如在下面

    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只是类型数组上length属性的get方法。

    这里的表达式asdf5是有效的,因为para5的类型是int[]但asdf6 没有 因为para6的类型只是类型array。ASDF7确实有效。

    我想要的是有效地使用ldlen指令,它只需要一个对象,而不是调用一个方法。这仅仅是表达式树库的一个限制吗?

    您可以使用反射编辑字段,甚至可以编译表达式!但尝试运行委托将导致操作可能会破坏运行时异常的稳定性。

    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 回复  |  直到 6 年前
        1
  •  5
  •   xanatos    6 年前

    在.NET中有两种类型的数组:一维的、基于零的(第一个索引是0)数组( int[] 例如,由IL语言直接支持的 Expression 类)(作为旁注,它们被称为sz数组)和其他数组(多维数组,例如 int[5,3] 以及第一个索引与现有索引不同的数组,以与支持它们的某些语言(例如旧的vb)兼容,这些语言在IL语言中没有直接支持,但由使用对 Array 类。它们基于两个不同的类,这两个类都是 数组 . 正如我所写,低级的IL指令只适用于一维的、基于零的指令。安 数组 对象可以两者都是,因此没有来自的支持 Expression.ArrayLength .

    你可以很容易地看到这个 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
    

    只是出于好奇,使用了一个未记录的功能(我们在这里玩火!)…

    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();
    

    在哪里? Conv 是:

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

    使用方法如下:

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

    这里的“诀窍”是通过 FieldOffest 我们可以强制转换具有相同内存布局的不兼容类型。所有sz数组共享相同的头格式(其中 Length 是包含的),所以我们“转换”我们的 数组 byte[] 数组(请注意,我们可能已经将其转换为任何其他类型,但在 byte :它是最小的可用类型,因此我们确信不能以任何方式“走出”阵列)。

    表达式树类似于:

    static int GetLength(Array array)
    {
        return new Conv { Array = array }.Array2.Length;
    }