得到数组的长度

时间:2015-03-11 13:35:30

标签: c# .net arrays reflection

所以我上了这堂课:

    public class MyClass{
        public int[] myArray = new int[9];
    }

我可以在不需要初始化MyClass实例的情况下获取数组的长度吗?

7 个答案:

答案 0 :(得分:8)

  

无需初始化MyClass的实例

没有。 = new int[9]被移动到构造函数,只有在实例化类时才会运行。

答案 1 :(得分:3)

你可以这样做:

public class MyClass {
    public static readonly int arrayLength = 9;
    public int[] myArray = new int[MyClass.arrayLength];
}

然后:

MyClass.arrayLength

答案 2 :(得分:3)

你可以解析构造函数的IL代码......

var constructor = typeof(MyClass).GetConstructor(Type.EmptyTypes);
var body = constructor.GetMethodBody();
byte[] il = body.GetILAsByteArray();

在这里你得到了IL代码......然后你需要一个IL解析器,那么你应该得到类似的东西:

IL_0000: ldarg.0
IL_0001: ldc.i4.5
IL_0002: newarr [mscorlib]System.Int32
IL_0007: stfld int32[] ExpressionProblem.MyClass::MyArray
IL_000c: ldarg.0

然后你开始从int32[] ExpressionProblem.MyClass::MyArray

回溯

嘿......我没有告诉你这是一个实用的解决方案。这是可能的解决方案。

好的......我准备了一个有效的例子:

/// <summary>
/// Supports only direct array sizing with values 0...int.MaxValue .
/// Doesn't support: values greater than int.MaxValue, static values,
/// function calling, ...
/// </summary>
/// <param name="type"></param>
/// <param name="arrayName"></param>
/// <param name="instance"></param>
/// <returns></returns>
public static int GetSize(Type type, string arrayName, bool instance)
{
    BindingFlags bindingFlags = (instance ? BindingFlags.Instance : BindingFlags.Static) | BindingFlags.Public | BindingFlags.NonPublic;

    // The array
    FieldInfo arrayField = type.GetField(arrayName, bindingFlags);

    // We don't know which constructor does the initialization, so we 
    // check each one. We start with the first one, and then we will 
    // follow the chain of constructors
    ConstructorInfo constructor = type.GetConstructors(bindingFlags).FirstOrDefault();

    while (constructor != null)
    {
        ConstructorInfo nextConstructor = null;

        var instructions = Mono.Reflection.Disassembler.GetInstructions(constructor);

        int i;

        for (i = 0; i < instructions.Count; i++)
        {
            if (instructions[i].OpCode == OpCodes.Call)
            {
                nextConstructor = instructions[i].Operand as ConstructorInfo;

                // If there is a call to another constructor, then 
                // this isn't the method we are looking for :-)
                if (nextConstructor != null)
                {
                    if (constructor.DeclaringType != nextConstructor.DeclaringType)
                    {
                        // Going to base class constructor without 
                        // initializing the field we are interested 
                        // in. We can stop looking.
                        nextConstructor = null;
                    }

                    i = instructions.Count;
                    break;
                }
            }

            // We look for a Stfld operation on the array
            if (instructions[i].OpCode == OpCodes.Stfld && (instructions[i].Operand as FieldInfo) == arrayField)
            {
                break;
            }
        }

        // Access to the array wasn't found. Let's look at the next 
        // constructor
        if (i == instructions.Count)
        {
            constructor = nextConstructor;
            continue;
        }

        // There are too few instructions before this array access
        if (i - 2 < 0)
        {
            throw new NotSupportedException();
        }

        OpCode newArr = instructions[i - 1].OpCode;

        // Is the previous instruction a NewArr?
        if (newArr != OpCodes.Newarr)
        {
            throw new NotSupportedException();
        }

        var sizeInstruction = instructions[i - 2];

        // Calc the size. There are various opcodes for this.
        int size;

        if (sizeInstruction.OpCode == OpCodes.Ldc_I4)
        {
            size = (int)sizeInstruction.Operand;
        }
        else if (sizeInstruction.OpCode == OpCodes.Ldc_I4_0)
        {
            size = 0;
        }
        else if (sizeInstruction.OpCode == OpCodes.Ldc_I4_1)
        {
            size = 1;
        }
        else if (sizeInstruction.OpCode == OpCodes.Ldc_I4_2)
        {
            size = 2;
        }
        else if (sizeInstruction.OpCode == OpCodes.Ldc_I4_3)
        {
            size = 3;
        }
        else if (sizeInstruction.OpCode == OpCodes.Ldc_I4_4)
        {
            size = 4;
        }
        else if (sizeInstruction.OpCode == OpCodes.Ldc_I4_5)
        {
            size = 5;
        }
        else if (sizeInstruction.OpCode == OpCodes.Ldc_I4_6)
        {
            size = 6;
        }
        else if (sizeInstruction.OpCode == OpCodes.Ldc_I4_7)
        {
            size = 7;
        }
        else if (sizeInstruction.OpCode == OpCodes.Ldc_I4_8)
        {
            size = 8;
        }
        else if (sizeInstruction.OpCode == OpCodes.Ldc_I4_M1)
        {
            size = -1;
        }
        else if (sizeInstruction.OpCode == OpCodes.Ldc_I4_S)
        {
            size = (sbyte)sizeInstruction.Operand;
        }
        else
        {
            // The size of the array was calculated in some other 
            // way. Not supported :-(
            throw new NotSupportedException();
        }

        return size;
    }

    throw new NotSupportedException();
}

它使用Mono.Reflection。请注意,它仅适用于使用常量值调整数组大小的简单情况。没有函数调用来初始化它,没有引用其他字段来初始化它。什么都没有。

称之为:

int size = GetSize(typeof(MyClass), "MyArray", true /* false for static fields */);

请注意,我认为这是一个好主意,除非您真的需要它:)

答案 3 :(得分:2)

如果不创建实例或使成员静态,则无法实现。

FieldInfo(通过反射)将为您提供这样一个事实:它是一个int32 [],但它可以得到它。

答案 4 :(得分:1)

如果长度是恒定的(因为它看起来像你的例子,因为它似乎没有使用在构造函数中作为参数提供的任何值),你可以使长度成为一个常量字段并在任何时候,因为常量是隐式的。

或者,正如有人在评论中所说,使用提供此信息但不引入新成员的属性来标记您的课程也是实现此目的的有效惯用方法。

另一方面,如果长度不是常数但你的场景仍然足够简单,你可以使用像Mono Cecil这样的库,反思程序集,找到这个特定类型及其构造函数,检查构造函数&#39; s IL并推断它运行时它将放在堆栈上的值。这完全有可能,但很痛苦。

答案 5 :(得分:1)

不是真的,但你可以实例化不使用&#39; MyClass&#39;。 然后获取数组的值。

创建一个接口IMyClass并使用Activator实例化该类,如下所示:

var _type = typeof(IMyClass);

var _types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(_s => _s.GetTypes()).Where(_p => _type.IsAssignableFrom(_p) && !_p.IsInterface);

foreach (var _instance in _types)
{
    var _instance = (IMyClass)Activator.CreateInstance(_instance));
    _instance.myArray.Length; 
}

答案 6 :(得分:0)

实际上,有一种方法可以做到这一点。看这里的汇编代码

.method public hidebysig specialname rtspecialname 
    instance void  .ctor() cil managed
// SIG: 20 00 01
{
  // Method begins at RVA 0x2050
  // Code size       21 (0x15)
  .maxstack  8
  IL_0000:  /* 02   |                  */ ldarg.0
  IL_0001:  /* 1F   | 09               */ ldc.i4.s   9
  IL_0003:  /* 8D   | (01)000013       */ newarr     [mscorlib]System.Int32
  IL_0008:  /* 7D   | (04)000001       */ stfld      int32[] ConsoleApplication2.MyClass::myArray
  IL_000d:  /* 02   |                  */ ldarg.0
  IL_000e:  /* 28   | (0A)000011       */ call       instance void [mscorlib]System.Object::.ctor()
  IL_0013:  /* 00   |                  */ nop
  IL_0014:  /* 2A   |                  */ ret

} //方法MyClass ::。ctor

的结尾

我们对这两条指令感兴趣

ldc.i4.s   9
newarr     [mscorlib]System.Int32

然后使用反射,我们可以为构造函数获取MethodBody,我们可以从中获取IL字节。问题是反射实际上并没有让你从IL的角度看待现有的IL,而只是发出它。但是,我们知道我们需要的指令的字节是什么,所以。

ConstructorInfo构造函数= typeof(MyClass).GetConstructors()。First();

byte[] constructorBytes = constructor.GetMethodBody().GetILAsByteArray();

int length = constructorBytes[Array.FindIndex(constructorBytes, b => b == 0x1F) + 1];

此代码的问题在于,对于不同的数字,它将是不同的ldc.i4,因此只有在长度介于9到256之间时它才会起作用。

当然,我理解这只适用于这个确切的代码,但通过一些修改和检查,有一种方法可以使它适用于几乎所有知道数组名称的类。

此外,我知道没有理由这样做,但是这里的每个人都在说这是不可能的,这不是非常正确