【问题标题】:Expression getting length of an array表达式获取数组的长度
【发布时间】:2018-05-21 19:02:10
【问题描述】:

如果您有一个类型为数组但不是特定数组类型(如 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 只是 Array 类型的 Length 属性的 get 方法。

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

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

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

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

【问题讨论】:

    标签: c# arrays expression-trees cil


    【解决方案1】:

    .NET 中有两种类型的数组,一维、从零开始(第一个索引为 0)的数组(例如 int[])由 IL 语言直接支持(以及 @987654324 @class)(作为旁注,它们被称为 SZ 数组)和其他数组(多维数组,例如 int[5,3] 和第一个索引不同的数组,以与某些支持它们的语言兼容,例如旧的VB),它们在 IL 语言中没有直接支持,但由使用Array 类调用的编译器支持。它们基于两个不同的类,它们都是Array 的子类。正如我所写的,低级 IL 指令仅适用于一维、从零开始的指令。 Array 对象可能两者兼有,因此 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),因此我们将 Array“转换”为 byte[] 数组(请注意,我们可能已将其转换为任何其他类型,但是在byte 中是一个明显的优势:它是最小的可用类型,因此我们确信我们不能以任何方式“退出”数组)。

    表达式树类似于:

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

    【讨论】:

    • 有趣,但从 cil 的角度来看,只有一个对象类型(以及 int32、int64、F、用户定义的值类型和本机 int)。 Ldlen 只是在 CIL 中获取一个对象,不是吗?
    • 当操作数的静态类型是数组时,我想编译一个 Expression.Arraylength 是不安全的。
    • @Nick msdn 上对ldlen 的描述非常清楚:将一个从零开始的一维数组的元素个数推到求值堆栈上。。如果您在其他类型的数组上使用,则未定义您获得的内容。如果您传递一个实际上是 sz 数组的 Array 对象,那么它将起作用(因为在 ArrayT[] 之间进行转换不会更改引用),但如果 Array 实际上是“特殊”数组,那么不好的事情就会发生。但目前尚不清楚/未定义真正会发生什么。
    • xanatos 感谢您的帮助。规范说您可以在对象类型上运行 ldlen,但正确的 CIL 意味着操作数必须是一维数组或 null。
    • @Nick 事实上 ldlen 甚至适用于多维数组,返回 x*y 大小
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-05
    • 2015-08-14
    • 1970-01-01
    • 2023-04-05
    • 2022-09-23
    • 2013-10-02
    • 2010-09-17
    相关资源
    最近更新 更多