Search code examples
.netgenericsreflectioncode-generationanalysis

Is it possible to find all concrete types of generic method calls in .NET code?


I'm writing a C# code generator for serialization of objects in order to send them over the network.

The starting point is this (simplified):

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)}!");
    }
}

Now the serialization code generator will generate additional non-generic Serialize and Deserialize methods for all types that need serialization, like this for a struct Vector3 with public float fields x, y and z:

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();
    }
}

The generic methods shown in the beginning are only there to prevent compiler errors in cases where the serialization code has not (yet) been generated for a type. I need the code to compile because otherwise I can't use reflection on it.

Currently I have to mark the types that need serialization code, using a custom EnableSerialization attribute.

Ideally, the generator would look at the compiled code (using static code analysis), identify the types that can possibly be passed to the generic Serialize and Deserialize methods and then generate code for those types. So, for example, if I have this somewhere in my code:

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

Then the code generator should pick up int as a type that needs serialization code.

Are there any recipes for this kind of endeavour, or is there anything in the .NET library or third-party libraries that can facilitate this?

(I have considered run-time code generation, but I'd prefer having it as a pre-processing step.)


Solution

  • using Mono.Reflection, you can do the following:

    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);
            }
          }
        }
      }
    }
    

    Both Disassembler and Instruction are part of Mono.Reflection.
    Now, you have all the type used passed to Serialization.Serialize in types.