【问题标题】:Is it possible to find all concrete types of generic method calls in .NET code?是否可以在 .NET 代码中找到所有具体类型的泛型方法调用?
【发布时间】:2019-12-15 20:27:13
【问题描述】:

我正在编写一个用于序列化对象的 C# 代码生成器,以便通过网络发送它们。

起点是这样的(简化的):

public static partial class Serialization
{
    public static void Serialize<T>(in T value, DataStream stream)
    {
        throw new NotImplementedException($"Don't know how to serialize type {typeof(T)}!");
    }

    public static void Deserialize<T>(out T target, DataStream stream)
    {
        throw new NotImplementedException($"Don't know how to deserialize type {typeof(T)}!");
    }
}

现在,序列化代码生成器将为所有需要序列化的类型生成额外的非泛型 SerializeDeserialize 方法,例如带有公共 float 字段 xy 的结构 Vector3z:

public static partial class Serialization
{
    // automatically generated method
    public static void Serialize(in Vector3 value, DataStream stream)
    {
        stream.Write(value.x);
        stream.Write(value.y);
        stream.Write(value.z);
    }

    // automatically generated method
    public static void Deserialize(out Vector3 target, DataStream stream)
    {
        target.x = stream.ReadFloat();
        target.y = stream.ReadFloat();
        target.z = stream.ReadFloat();
    }
}

开头显示的泛型方法仅用于在尚未(尚未)为某个类型生成序列化代码的情况下防止编译器错误。我需要编译代码,否则我无法对其使用反射。

目前我必须使用自定义EnableSerialization 属性标记需要序列化代码的类型。

理想情况下,生成器会查看编译后的代码(使用静态代码分析),识别可能传递给通用 SerializeDeserialize 方法的类型,然后为这些类型生成代码。因此,例如,如果我的代码中有这个:

int x = 42;
Serialization.Serialize(x, new DataStream());

那么代码生成器应该选择int作为需要序列化代码的类型。

是否有这种努力的秘诀,或者 .NET 库或第三方库中是否有任何东西可以促进这种努力?

(我考虑过运行时代码生成,但我更喜欢将其作为预处理步骤。)

【问题讨论】:

  • 不可能?也许不是?超级难,极其困难和复杂?当然。您必须反编译代码,并考虑通过它的所有可能 路径,并跟踪多种类型的&lt;T&gt; 用法,以便能够安全地 执行此操作。保留属性或其他一些标记概念。远,FAR,不值得尝试这样做,我不确定你是否能做到 100% 正确。
  • 您的代码生成器是否可以为 all 类型正确创建序列化和反序列化代码?它可以在运行时、动态、即时生成中使用吗?
  • @LasseVågsætherKarlsen:它适用于相当简单的情况,即按顺序序列化所有字段就足够了(使用反射设置非公共字段)。我现在认为 ildasm.exe 工具可能有用。它显示了对我的泛型方法的所有调用,包括泛型参数。因此,也许在我的程序集上调用 ildasm.exe 并将输出转到文本文件,然后扫描文本文件以查找方法调用是一个好主意。
  • ...当然,在可能的多种类型中跟踪泛型参数仍然是一个巨大的挑战...

标签: .net generics reflection code-generation analysis


【解决方案1】:

使用Mono.Reflection,您可以执行以下操作:

HashSet<Type> types = new HashSet<Type>();
Assembly assembly = Assembly.LoadFile(@"<Path>");

foreach (Module module in assembly.GetModules())
{
  foreach (Type type in module.GetTypes())
  {
    // GetMethodBody() should not be null since otherwise Disassembler.GetInstructions would throw an exception
    foreach (MethodInfo method in type.GetMethods().Where(m => m.GetMethodBody() != null))
    {
      foreach (Instruction instruction in Disassembler.GetInstructions(method))
      {
        // instruction.Operand being MethodInfo most probably means a call instrution.
        // Not sure if this always true
        MethodInfo called = instruction.Operand as MethodInfo;

        if (called != null && called.DeclaringType.Name.Equals("Serialization") && called.Name.Equals("Serialize"))
        {
          types.Add(called.GetParameters()[0].ParameterType);
        }
      }
    }
  }
}

DisassemblerInstruction 都是 Mono.Reflection 的一部分。
现在,您已将所有使用的类型传递给 types 中的 Serialization.Serialize

【讨论】:

  • 太好了,这似乎工作得很好。谢谢!可以将 BindingFlags.NonPublic 等添加到 GetMethods() 调用中,以获取所有方法,而不仅仅是公共方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-04-10
  • 1970-01-01
  • 2011-12-17
  • 2014-09-05
  • 2022-01-03
  • 1970-01-01
  • 2021-07-28
相关资源
最近更新 更多