2013-06-12 21 views
5

Tôi đang cố gắng sử dụng Cecil để tìm các phiên bản của các cuộc gọi đến một phương thức chung bằng cách sử dụng một giao diện cho một bài kiểm tra quy ước. Tôi gặp sự cố khi xác định loại chung từ MethodReference.Làm thế nào tôi có thể sử dụng Cecil để tìm loại được chuyển đến một phương pháp chung?

tôi đã thiết lập một thử nghiệm cơ bản:

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

Vì vậy, tôi muốn tìm ra lời kêu gọi farm.Add<IAnimal>(), mà sẽ không đưa ra một lỗi thời gian biên dịch hoặc thậm chí là một lỗi thời gian chạy, cho đến khi trang trại conceivable đã cố gắng tạo ra một thể hiện của kiểu thông qua sự phản chiếu. Trường hợp sử dụng thực tế của tôi là kiểm tra quy ước cho vùng chứa DI.

Cecil do thỏa thuận với nó trong phương pháp 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")) 
     ; 
} 

Phần callsInError là nơi tôi đang thất bại trong việc xác định loại chung được sử dụng khi gọi Farm::Add. Cụ thể, thuộc tính GenericParameters của MethodReference trống, vì vậy GenericParameters[0] cho một ArgumentOutOfRangeException. Tôi đã khám phá các MethodReference, và tôi chắc chắn nhận được các cuộc gọi đến Farm::Add, nhưng tôi không thể nhìn thấy bất cứ nơi nào có liên quan đến loại chung được sử dụng ngoại trừ các tài sản FullName, mà không phải là hữu ích.

Làm thế nào tôi có thể yêu cầu Cecil xác định loại chung được sử dụng trong cuộc gọi?

Trả lời

3

Nếu tôi cast MethodReference đến một GenericInstanceMethod tham số GenericArguments làm những gì tôi cần:

var callsInError = testCases 
    .Where(test => !((GenericInstanceMethod)test).GenericArguments[0].Resolve().IsClass) 
    ; 
Các vấn đề liên quan