C# - 获取在 class A 中使用的所有类型
C# - Get all types that been used in class A
如何获取特定类型中使用的所有类型?
class "MyClass" 的示例:
[MyAttribute(new OtherType(TestEnum.EnumValue1))]
public class MyClass:MyOtherClass
{
public Type MyType { get; set; }
public string MyString { get; set; }
private DateTime MyDateTime;
[OtherAttribute()]
public int MyMethod(double doubleNumber, float floatNumber)
{
justMyClass myJustClass = new justMyClass();
return (int)doubleNumber / (int)floatNumber + myJustClass.Count();
}
}
我想要获取类型:MyAttribute、OtherType、TestEnum、MyClass、MyOtherClass、Type、string、DateTime、OtherAttribute、int、double、float 和 justMyClass。
有什么办法吗?
[MyAttribute(new OtherType(TestEnum.EnumValue1))]
那是无效的,你必须在属性构造函数中有一个常量。撇开这个不谈,大部分内容都很简单,虽然有点冗长。
可以调用typeof(MyClass).CustomAttributes.Select(ca => ca.AttributeType)
获取属性类型,typeof(MyClass).GetFields().Select(fi => fi.FieldType)
获取字段类型等。 Union
这些加在一起,您将拥有签名和属性中的所有类型。
为 MyMethod
获取 justMyClass
比较棘手(获取 double
和 float
很容易,当您执行 typeof(MyClass).GetMethods().SelectMany(mi => mi.GetParameters()).Select(pa => pa.ParameterType)
时它们会出现,同样int
对于 return 类型)。
在调试版本中,我们可以期望该方法编译成如下所示:
.method public hidebysig instance int32 MyMethod (float64 doubleNumber, float32 floatNumber) cil managed
{
.custom instance void Temp.Program/OtherAttribute::.ctor() = (
01 00 00 00
)
.maxstack 2
.locals init (
[0] class Temp.Program/justMyClass myClass,
[1] int32 ret
)
nop
newobj instance void Temp.Program/justMyClass::.ctor()
stloc.0
ldarg.1
conv.ovf.i4
ldarg.2
conv.ovf.i4
div
stloc.1
ret
}
我们可以简单地得到 int
和 justMyClass
如果我们这样做的话:
typeof(MyClass)
.GetMethods()
.Select(mi => mi.GetMethodBody())
.Where(mb => mb != null)
.SelectMany(mb => mb.LocalVariables)
.Select(lv => lv.LocalType)
然而,对于发布版本,我们希望它被编译成更像这样的东西:
.method public hidebysig instance int32 MyMethod (float64 doubleNumber, float32 floatNumber) cil managed
{
.custom instance void Temp.Program/OtherAttribute::.ctor() = (
01 00 00 00
)
.maxstack 8
newobj instance void Temp.Program/justMyClass::.ctor()
pop
ldarg.1
conv.i4
ldarg.2
conv.i4
div
ret
}
并且由于未使用调用 new justMyClass()
的结果,因此不会将其存储在本地以供调试之用。即使是使用过的值也可能只是从它在堆栈上的位置开始使用,而不是存储在本地数组中,因此更实际的代码可能会导致类型丢失。相反,您必须实际拆解调用。这种方法的开始是:
private static IEnumerable<Type> GetUsedTypes(Type type, MethodInfo mi)
{
var body = mi.GetMethodBody();
if(body == null) // not managed code
yield break;
var cil = body.GetILAsByteArray();
for(int idx = 0; idx < cil.Length; ++idx)
{
switch(cil[idx])
{
case 0x73: // newobj
int token = cil[++idx];
token |= (int)cil[++idx] << 8;
token |= (int)cil[++idx] << 16;
token |= (int)cil[++idx] << 24;
yield return type.Module.ResolveMethod(token).DeclaringType;
break;
/* TODO: Other opcodes that deal with types */
}
}
}
上面的这种情况仅通过检查后面的构造函数来处理 newobj
,而不是检查泛型类型参数等。必须扩展该方法以处理 call
, callvirt
等等,并确保它没有看到 0x73
实际上是另一个令牌的一部分,等等。这是相当多的工作,但以上足以表明它可以工作(在这种情况下确实给出了正确答案)。
尽管如此,在三种情况下,这并不会像预期的那样。
在代码中找一个;如果有 internalcall
你将不会得到任何方法体来检查。
另一个是您可能会对更复杂的情况感到惊讶,例如:
public void HasNoType()
{
if(false)
throw new Exception("Impossible");
}
虽然源代码有一个 bool
、一个 string
和一个 Exception
,但在编译时死代码将被删除,因此调试编译可能有一个 bool
(所以我们可以在调试器中看到 false
),而发布编译将什么都没有,只是一个直接的 ret
.
再进一步的情况是:
public bool CheckIsInRangeWhenAlreadyWeKnowSizeIsNotNegative(int idx, int size)
{
return (uint)idx < (uint)size;
}
从源头上看,这似乎是在使用 uint
,但实际上并非如此。相反,生成的 CIL 是:
.method public hidebysig instance bool CheckIsInRangeWhenAlreadyWeKnowSizeIsNotNegative (int32 idx, int32 size) cil managed
{
.maxstack 8
ldarg.1
ldarg.2
clt.un
ret
}
在 CIL 中,堆栈上的 32 位整数不完全是 Int32
或 UInt32
,但根据对它们执行的操作不同,其行为也有所不同,因此没有转换,也无处可去使用 uint
的代码,但它们只是以无符号的方式进行比较,在 C# 中最接近的是转换为 uint
然后进行比较。因此,在检查方法体时,您不会在其中找到 uint
,除非您进一步进行反汇编和实际反编译,并发现在 C# 中表达它的唯一方法是使用 uint
。
因此,虽然获取属于签名和属性的所有类型很容易(如果相当复杂的话),但获取所有本地使用的类型要么非常困难,要么不可能,具体取决于您对本地的看法使用类型。
如何获取特定类型中使用的所有类型?
class "MyClass" 的示例:
[MyAttribute(new OtherType(TestEnum.EnumValue1))]
public class MyClass:MyOtherClass
{
public Type MyType { get; set; }
public string MyString { get; set; }
private DateTime MyDateTime;
[OtherAttribute()]
public int MyMethod(double doubleNumber, float floatNumber)
{
justMyClass myJustClass = new justMyClass();
return (int)doubleNumber / (int)floatNumber + myJustClass.Count();
}
}
我想要获取类型:MyAttribute、OtherType、TestEnum、MyClass、MyOtherClass、Type、string、DateTime、OtherAttribute、int、double、float 和 justMyClass。
有什么办法吗?
[MyAttribute(new OtherType(TestEnum.EnumValue1))]
那是无效的,你必须在属性构造函数中有一个常量。撇开这个不谈,大部分内容都很简单,虽然有点冗长。
可以调用typeof(MyClass).CustomAttributes.Select(ca => ca.AttributeType)
获取属性类型,typeof(MyClass).GetFields().Select(fi => fi.FieldType)
获取字段类型等。 Union
这些加在一起,您将拥有签名和属性中的所有类型。
为 MyMethod
获取 justMyClass
比较棘手(获取 double
和 float
很容易,当您执行 typeof(MyClass).GetMethods().SelectMany(mi => mi.GetParameters()).Select(pa => pa.ParameterType)
时它们会出现,同样int
对于 return 类型)。
在调试版本中,我们可以期望该方法编译成如下所示:
.method public hidebysig instance int32 MyMethod (float64 doubleNumber, float32 floatNumber) cil managed
{
.custom instance void Temp.Program/OtherAttribute::.ctor() = (
01 00 00 00
)
.maxstack 2
.locals init (
[0] class Temp.Program/justMyClass myClass,
[1] int32 ret
)
nop
newobj instance void Temp.Program/justMyClass::.ctor()
stloc.0
ldarg.1
conv.ovf.i4
ldarg.2
conv.ovf.i4
div
stloc.1
ret
}
我们可以简单地得到 int
和 justMyClass
如果我们这样做的话:
typeof(MyClass)
.GetMethods()
.Select(mi => mi.GetMethodBody())
.Where(mb => mb != null)
.SelectMany(mb => mb.LocalVariables)
.Select(lv => lv.LocalType)
然而,对于发布版本,我们希望它被编译成更像这样的东西:
.method public hidebysig instance int32 MyMethod (float64 doubleNumber, float32 floatNumber) cil managed
{
.custom instance void Temp.Program/OtherAttribute::.ctor() = (
01 00 00 00
)
.maxstack 8
newobj instance void Temp.Program/justMyClass::.ctor()
pop
ldarg.1
conv.i4
ldarg.2
conv.i4
div
ret
}
并且由于未使用调用 new justMyClass()
的结果,因此不会将其存储在本地以供调试之用。即使是使用过的值也可能只是从它在堆栈上的位置开始使用,而不是存储在本地数组中,因此更实际的代码可能会导致类型丢失。相反,您必须实际拆解调用。这种方法的开始是:
private static IEnumerable<Type> GetUsedTypes(Type type, MethodInfo mi)
{
var body = mi.GetMethodBody();
if(body == null) // not managed code
yield break;
var cil = body.GetILAsByteArray();
for(int idx = 0; idx < cil.Length; ++idx)
{
switch(cil[idx])
{
case 0x73: // newobj
int token = cil[++idx];
token |= (int)cil[++idx] << 8;
token |= (int)cil[++idx] << 16;
token |= (int)cil[++idx] << 24;
yield return type.Module.ResolveMethod(token).DeclaringType;
break;
/* TODO: Other opcodes that deal with types */
}
}
}
上面的这种情况仅通过检查后面的构造函数来处理 newobj
,而不是检查泛型类型参数等。必须扩展该方法以处理 call
, callvirt
等等,并确保它没有看到 0x73
实际上是另一个令牌的一部分,等等。这是相当多的工作,但以上足以表明它可以工作(在这种情况下确实给出了正确答案)。
尽管如此,在三种情况下,这并不会像预期的那样。
在代码中找一个;如果有 internalcall
你将不会得到任何方法体来检查。
另一个是您可能会对更复杂的情况感到惊讶,例如:
public void HasNoType()
{
if(false)
throw new Exception("Impossible");
}
虽然源代码有一个 bool
、一个 string
和一个 Exception
,但在编译时死代码将被删除,因此调试编译可能有一个 bool
(所以我们可以在调试器中看到 false
),而发布编译将什么都没有,只是一个直接的 ret
.
再进一步的情况是:
public bool CheckIsInRangeWhenAlreadyWeKnowSizeIsNotNegative(int idx, int size)
{
return (uint)idx < (uint)size;
}
从源头上看,这似乎是在使用 uint
,但实际上并非如此。相反,生成的 CIL 是:
.method public hidebysig instance bool CheckIsInRangeWhenAlreadyWeKnowSizeIsNotNegative (int32 idx, int32 size) cil managed
{
.maxstack 8
ldarg.1
ldarg.2
clt.un
ret
}
在 CIL 中,堆栈上的 32 位整数不完全是 Int32
或 UInt32
,但根据对它们执行的操作不同,其行为也有所不同,因此没有转换,也无处可去使用 uint
的代码,但它们只是以无符号的方式进行比较,在 C# 中最接近的是转换为 uint
然后进行比较。因此,在检查方法体时,您不会在其中找到 uint
,除非您进一步进行反汇编和实际反编译,并发现在 C# 中表达它的唯一方法是使用 uint
。
因此,虽然获取属于签名和属性的所有类型很容易(如果相当复杂的话),但获取所有本地使用的类型要么非常困难,要么不可能,具体取决于您对本地的看法使用类型。