5

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。具体的には、 のGenericParametersプロパティMethodReferenceが空であるため、 が返さGenericParameters[0]れますArgumentOutOfRangeException。を調査しましたがMethodReference、間違いなく への呼び出しを取得していますがFarm::Add、使用されているジェネリック型に関連するものはどこにもありませんが、FullNameプロパティは役に立ちません。

呼び出しで使用されているジェネリック型を Cecil に識別させるにはどうすればよいですか?

4

1 に答える 1

3

をパラメーターにキャストするMethodReferenceと、GenericInstanceMethod必要GenericArgumentsなことは実行されます。

var callsInError = testCases
    .Where(test => !((GenericInstanceMethod)test).GenericArguments[0].Resolve().IsClass)
    ;
于 2013-06-12T21:19:38.473 に答える