C# - 获取 A 类中使用的所有类型

标签 c# reflection types

如何获取特定类型中使用的所有类型?

类“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 比较棘手(获取 doublefloat 很容易,它们会出现当你执行 typeof(MyClass).GetMethods().SelectMany(mi => mi.GetParameters()).Select(pa => pa.ParameterType) 和同样的 int返回类型)。

在调试构建中,我们可以期望该方法编译成如下所示:

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

如果我们完成了,我们可以很简单地得到 intjustMyClass:

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,通过检查后面的构造函数,而不是检查泛型类型参数等。该方法必须扩展以处理 调用callvirt 等等,并确保它没有看到实际上是另一个 token 的一部分的 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 位整数不完全是 Int32UInt32,但根据对它们执行的操作不同,其行为也有所不同,因此没有转换并且在代码中没有使用 uint 的地方,但它们只是以无符号的方式进行比较,在 C# 中最接近的是转换为 uint 然后进行比较.因此,在检查方法体时,您不会在其中找到 uint,除非您进一步进行反汇编和实际反编译,并发现在 C# 中表达这一点的唯一方法是使用 单位

因此,虽然获取属于签名和属性的所有类型很容易(如果相当复杂),但获取所有本地使用的类型要么非常困难,要么不可能,具体取决于您认为本地使用的类型.

关于C# - 获取 A 类中使用的所有类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30983835/

相关文章:

c# - 在遍历表达式时提取实例变量的当前值

Java:函数可以返回调用类和方法名称的任何简单方法?

php - 检查可迭代内容 PHP

c - 如何检查输入是否为 C 中的数字?

c# - MVC Azure无法找到具有默认路由的 View 或主控,但可以在本地工作

c# - C# 和 JavaScript 正则表达式之间的区别?

c# - 两个数组的元素引用一个对象

c# - 编码的 UI 测试 - 单击控件基于表行上的其他内容

c# - Xamarin.Android - typeof 和 GetType 始终返回 System.RuntimeType

尽管进行了彻底检查,但在 Modelsim 上的仿真中类型不匹配的 VHDL 代码