Выражение, получающее длину массива - PullRequest
0 голосов
/ 21 мая 2018

Если у вас есть выражение типа 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 - это просто метод get для свойства Length типа Array.

Здесь выражение 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 Ответ

0 голосов
/ 21 мая 2018

В .NET есть два типа массивов: одномерные, начинающиеся с нуля (первый индекс равен 0) (например, int[]), которые поддерживаются непосредственно языком IL (и * 1002).* 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;
}
...