如何使用Cecil查找传递给泛型方法的类型?

本文关键字:泛型方法 类型 何使用 Cecil 查找 | 更新日期: 2023-09-27 18:18:04

我试图使用Cecil来查找使用约定测试接口调用泛型方法的实例。我在从MethodReference中识别通用类型时遇到了麻烦。

我设置了一个基本的测试:

private interface IAnimal
{
}
private class Duck : IAnimal
{
}
private class Farm
{
    private readonly ICollection<string> _animals = new List<string>();
    public void Add<T>()
    {
        _animals.Add(typeof(T).Name);
    }
    public override string ToString()
    {
        return string.Join(", ", _animals);
    }
}
static Farm FarmFactory()
{
    var farm = new Farm();
    farm.Add<Duck>();
    farm.Add<Duck>();
    farm.Add<IAnimal>();    // whoops
    farm.Add<Duck>();
    return farm;
}
private static void Main(string[] args)
{
    var farm = FarmFactory();
    Console.WriteLine("Farm:");
    Console.WriteLine(farm);
    // Use Cecil to find the call to farm.Add<IAnimal>():
    Console.WriteLine("Errors:");
    FindErrors();
    Console.Read();
}

所以我想找到对farm.Add<IAnimal>()的调用,它不会给出编译时错误甚至运行时错误,直到可以想象的农场试图通过反射创建该类型的实例。我的实际用例是一个DI容器的约定测试。

Cecil在FindErrors()方法中进入它:

private static void FindErrors()
{
    var methods = AssemblyDefinition.ReadAssembly(typeof (Farm).Assembly.Location)
                                    .Modules
                                    .SelectMany(module => module.Types)
                                    .SelectMany(type => type.Methods)
                                    .Where(method => method.HasBody)
                                    .ToArray()
        ;
    var callsToFarmDotAdd = methods
        .Select(method => new
            {
                Name = method.Name,
                MethodReferences = GetCallsToFarmDotAdd(method)
            })
        .Where(x => x.MethodReferences.Any())
        .ToArray()
        ;
    var testCases = callsToFarmDotAdd
        .SelectMany(x => x.MethodReferences)
        ;
    var callsInError = testCases
        .Where(test => !test.GenericParameters[0].Resolve().IsClass)
        ;
    foreach (var error in callsInError)
    {
        Console.WriteLine(error.FullName);
    }
}
private static IEnumerable<MethodReference> GetCallsToFarmDotAdd(MethodDefinition method)
{
    return method.Body.Instructions
                 .Where(instruction => instruction.OpCode == OpCodes.Callvirt)
                 .Select(instruction => (MethodReference) instruction.Operand)
                 .Where(methodReference => methodReference.FullName.Contains("Farm::Add"))
        ;
}

callsInError部分是我无法识别调用Farm::Add时使用的泛型类型的地方。具体来说,MethodReferenceGenericParameters属性为空,因此GenericParameters[0]给出ArgumentOutOfRangeException。我已经探索了MethodReference,我肯定会调用Farm::Add,但我看不到任何与使用的泛型类型相关的地方,除了FullName属性,这是没有用的。

如何让Cecil识别调用中使用的泛型类型?

如何使用Cecil查找传递给泛型方法的类型?

如果我将MethodReference转换为GenericInstanceMethod,则GenericArguments参数可以满足我的需要:

var callsInError = testCases
    .Where(test => !((GenericInstanceMethod)test).GenericArguments[0].Resolve().IsClass)
    ;