C# 获取数组的长度
所以我上了这门课:C# 获取数组的长度,c#,.net,arrays,reflection,C#,.net,Arrays,Reflection,所以我上了这门课: public class MyClass{ public int[] myArray = new int[9]; } 我可以在不需要初始化MyClass实例的情况下获取数组的长度吗 无需初始化MyClass的实例 否。=new int[9]被移动到构造函数,该构造函数仅在实例化类时运行。如果不创建实例或使成员为静态,则不可能 FieldInfo(通过反射)将为您提供一个事实,即它是一个int32[],但这是它所能得到的。如果长度是常量(从您的
public class MyClass{
public int[] myArray = new int[9];
}
我可以在不需要初始化MyClass实例的情况下获取数组的长度吗
无需初始化MyClass的实例
否。
=new int[9]
被移动到构造函数,该构造函数仅在实例化类时运行。如果不创建实例或使成员为静态,则不可能
FieldInfo(通过反射)将为您提供一个事实,即它是一个int32[],但这是它所能得到的。如果长度是常量(从您的示例中看起来,因为它似乎没有使用构造函数中作为参数提供的任何值),您可以将长度设置为常量字段,并随时访问它,因为常量是隐式静态的
或者,正如有人在评论中所说的,在类中标记一个属性,该属性在不引入新成员的情况下提供这些信息,这也是一种有效的惯用方法
另一方面,如果长度不是常数,但您的场景仍然足够简单,那么您可以使用类似Mono Cecil的库,对程序集进行反思,找到此特定类型及其构造函数,检查构造函数的IL,并推断如果它运行,它将在堆栈上放置的值。这是完全可能的,但是很痛苦。你可以这样做:
public class MyClass {
public static readonly int arrayLength = 9;
public int[] myArray = new int[MyClass.arrayLength];
}
然后:
不太可能,但可以不使用“MyClass”进行实例化。 然后获取数组的值 创建接口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;
}
您可以解析构造函数的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();
}
请注意,我不认为这是个好主意,除非你真的需要它:)事实上,有一种方法可以做到这一点。请看这里的汇编代码
.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 constructor=typeof(MyClass).GetConstructors().First()
此代码的问题在于,对于不同的数字,它的ldc.i4将不同,因此它仅在长度介于9和256之间时才起作用
当然,我知道这只适用于这个确切的代码,但是经过一些修改和检查,有一种方法可以使它适用于几乎任何知道数组名称的类
另外,我知道没有理由这样做,但这里的每个人都说这是不可能的,这是不对的。不,因为这不是该类型的静态属性,而是实例属性-实例是必需的!只是出于兴趣,你为什么需要知道这些?如果你需要一些编译时信息,属性是一种方法。但是有一种方法可以做到这一点。在编写构造函数中显式编写的任何其他代码之前,像这样的每次初始化都会被放入构造函数代码中。您可以通过使用反射并查看构造函数指令集来获得数组的长度。@Spo1ler这是一种可能性,但对于一般问题,它不是解决方案(也就是说:您肯定不希望这样)。OP很可能在这里解决了一个XY问题,其中Y是“因此我需要使用反射来获得数组长度,而不需要实例化类”。OP忘了提到问题X。因此,对于一个实例,您可以这样得到它:
((数组)typeof(MyClass).GetField(“myArray”).GetValue(new MyClass()).Length
是的,或者干脆new MyClass().myArray.Length
。。。不确定OP试图做什么。@Selman22:((数组)typeof(MyClass2).GetField(“myArray”,BindingFlags.Instance | BindingFlags.NonPublic).GetValue(new MyClass2()).Length代码>尝试使用新建MyClass().myArray.Length
;)来完成此操作你不理解,你仍然在创建一个类的实例,不管你是使用Activator
还是仅仅new
来创建它?是的,我理解,因此我说,“…但是你可以不使用“MyClass”来实例化它…”。在预先编辑好的问题中,有一些类似于反思的东西。这就是为什么我在回答中提到了这一点。嗯,你的回答肯定比我的更详细,很好。
.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
ldc.i4.s 9
newarr [mscorlib]System.Int32
byte[] constructorBytes = constructor.GetMethodBody().GetILAsByteArray();
int length = constructorBytes[Array.FindIndex(constructorBytes, b => b == 0x1F) + 1];