В .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;
}