2013-06-12 3 views
5

Я пытаюсь использовать Cecil для поиска экземпляров вызовов для общего метода с использованием интерфейса для тестового теста. У меня возникли проблемы с идентификацией родового типа от MethodReference.Как использовать Cecil для поиска типа, переданного в общий метод?

Я создал базовый тест:

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. В частности, свойство GenericParametersMethodReference пуста, поэтому GenericParameters[0] дает ArgumentOutOfRangeException. Я исследовал MethodReference, и я определенно получаю вызовы до Farm::Add, но я не вижу нигде, что относится к используемому родовому типу, за исключением свойства FullName, что не полезно.

Как я могу заставить Сесил идентифицировать общий тип, используемый в вызове?

ответ

3

Если бы я бросил MethodReference к GenericInstanceMethod: параметр GenericArguments делает то, что мне нужно:

var callsInError = testCases 
    .Where(test => !((GenericInstanceMethod)test).GenericArguments[0].Resolve().IsClass) 
    ; 
Смежные вопросы