2011-09-15 52 views
6

Tôi đang cố gắng xây dựng một mẫu T4 sẽ lấy các định nghĩa phương thức trong một giao diện và tái tạo chữ ký và gọi phương thức cơ bản với các tham số đã truyền. Giao diện định nghĩa vô số các phương thức để viết lại chúng mỗi lần thay đổi giao diện trở nên rất khó khăn. Một biến chứng khác là giao diện là một giao diện chung với các phương thức chung và các tham số chung có thể có. Cho đến nay, cách duy nhất tôi có thể tìm thấy để tái tạo chữ ký thực tế (không có các định nghĩa "1" cho generics) là hoàn toàn xây dựng lại nó, điều này trở nên rất cồng kềnh.Chữ ký thực tế sử dụng Reflection

Trong trường hợp tôi có một chữ ký như thế này trong giao diện của tôi:

ICar Drive<TCar>(Expression<Func<TWheel, bool>> wheels, int miles) 

Có cách nào để hoàn toàn sao chép rằng với sự phản ánh mà không cần phải disect toàn bộ chi tiết MethodInfo, hoặc là có một cách nhanh chóng để nhận được chuỗi ở trên để tôi có thể viết nó trong T4 của tôi?

Bất kỳ trợ giúp nào sẽ được đánh giá rất nhiều!

+3

Không phải là câu trả lời cho câu hỏi của bạn, nhưng trong kinh nghiệm của tôi trộn T4 và phản ánh về hội trong giải pháp của bạn sẽ kết thúc trong nước mắt. VS (hoạt động như bộ xử lý T4) sẽ tải các bộ phận trong bộ nhớ để phản ánh, và sau đó nó sẽ nhận được một "tập tin sử dụng" lỗi trong lần sau khi bạn xây dựng chúng. Đề xuất: tìm một cách khác. – Jon

+1

Bạn có thể truy cập mã nguồn của giao diện không? Việc trích xuất thông tin từ văn bản có thể dễ dàng hơn việc xây dựng lại nguồn với sự phản chiếu. – dtb

+0

Một lý do tại sao phương thức tạo chữ ký phương thức không được bao gồm trong .NET Framework là nó sẽ phải hỗ trợ nhiều ngôn ngữ, vì các assembly .NET có thể được gọi từ C#, VB.NET, J #, JScript, PowerShell, v.v. và mỗi người có một cú pháp khác nhau cho chữ ký phương thức của họ. – luksan

Trả lời

8

Khi tôi cần tạo mã, tôi thường tìm đến không gian tên System.CodeDom. Nó cho phép bạn xây dựng một biểu diễn logic của mã và sau đó lấy mã nguồn tương ứng cho những gì bạn đã xây dựng. Tuy nhiên, tôi không biết nếu tôi có thể nói rằng cách này cũng không phải là 'rườm rà' như bạn đã nói trong câu trả lời của bạn (và điều này chắc chắn liên quan đến 'mổ xẻ' các MethodInfo. Tuy nhiên, nó cung cấp cho bạn một nền tảng khá phong nha. bằng cách trong giao diện bạn muốn 'bản sao', tên của lớp mới và các lớp cơ sở mà bạn muốn mở rộng như vậy:

var code = GenerateCode(typeof(TestInterface<>), 
         "MyNewClass", 
         typeof(TestBaseClass<>)); 

sẽ cho kết quả này:

//------------------------------------------------------------------------------ 
// <auto-generated> 
//  This code was generated by a tool. 
//  Runtime Version:4.0.30319.237 
// 
//  Changes to this file may cause incorrect behavior and will be lost if 
//  the code is regenerated. 
// </auto-generated> 
//------------------------------------------------------------------------------ 

namespace MyNamespace { 
    using System; 
    using System.Linq.Expressions; 


    public class MyNewClass<TWheel> : TestInterface<TWheel>, TestBaseClass<TWheel> 
    { 

     public MyNamespace.ICar Drive<TCar>(Expression<Func<TWheel, bool>> wheels, int miles) 
     { 
      return base.Drive(wheels, miles); 
     } 
    } 
} 

Cũng , bạn có thể thay đổi một vài ký tự trong mã và chuyển sang nhà cung cấp VB và bạn sẽ nhận được kết quả Visual Basic (có lẽ không hữu ích nhưng khá thú vị):

'------------------------------------------------------------------------------ 
' <auto-generated> 
'  This code was generated by a tool. 
'  Runtime Version:4.0.30319.237 
' 
'  Changes to this file may cause incorrect behavior and will be lost if 
'  the code is regenerated. 
' </auto-generated> 
'------------------------------------------------------------------------------ 

Option Strict Off 
Option Explicit On 

Imports System 
Imports System.Linq.Expressions 

Namespace MyNamespace 

    Public Class MyNewClass(Of TWheel) 
     Inherits TestInterface(Of TWheel) 
     Implements TestBaseClass(Of TWheel) 

     Public Function Drive(Of TCar)(ByVal wheels As Expression(Of Func(Of TWheel, Boolean)), ByVal miles As Integer) As MyNamespace.ICar 
      Return MyBase.Drive(wheels, miles) 
     End Function 
    End Class 
End Namespace 

Đây là con thú GenerateCode. Hy vọng rằng những ý kiến ​​có thể giải thích những gì đang xảy ra:

public static string GenerateCode(Type interfaceType, string generatedClassName, Type baseClass) 
{ 
    //Sanity check 
    if (!interfaceType.IsInterface) 
     throw new ArgumentException("Interface expected"); 

    //I can't think of a good way to handle closed generic types so I just won't support them 
    if (interfaceType.IsGenericType && !interfaceType.IsGenericTypeDefinition) 
     throw new ArgumentException("Closed generic type not expected."); 

    //Build the class 
    var newClass = new CodeTypeDeclaration(generatedClassName) 
    { 
     IsClass = true, 
     TypeAttributes = TypeAttributes.Public, 
     BaseTypes = 
           { 
            //Include the interface and provided class as base classes 
            MakeTypeReference(interfaceType), 
            MakeTypeReference(baseClass) 
           } 
    }; 

    //Add type arguments (if the interface is generic) 
    if (interfaceType.IsGenericType) 
     foreach (var genericArgumentType in interfaceType.GetGenericArguments()) 
      newClass.TypeParameters.Add(genericArgumentType.Name); 

    //Loop through each method 
    foreach (var mi in interfaceType.GetMethods()) 
    { 
     //Create the method 
     var method = new CodeMemberMethod 
     { 
      Attributes = MemberAttributes.Public | MemberAttributes.Final, 
      Name = mi.Name, 
      ReturnType = MakeTypeReference(mi.ReturnType) 
     }; 

     //Add any generic types 
     if (mi.IsGenericMethod) 
      foreach (var genericParameter in mi.GetGenericArguments()) 
       method.TypeParameters.Add(genericParameter.Name); 

     //Add the parameters 
     foreach (var par in mi.GetParameters()) 
      method.Parameters.Add(new CodeParameterDeclarationExpression(MakeTypeReference(par.ParameterType), 
                      par.Name)); 

     //Call the same method on the base passing all the parameters 
     var allParameters = 
      mi.GetParameters().Select(p => new CodeArgumentReferenceExpression(p.Name)).ToArray(); 
     var callBase = new CodeMethodInvokeExpression(new CodeBaseReferenceExpression(), mi.Name, allParameters); 

     //If the method is void, we just call base 
     if (mi.ReturnType == typeof(void)) 
      method.Statements.Add(callBase); 
     else 
      //Otherwise, we return the value from the call to base 
      method.Statements.Add(new CodeMethodReturnStatement(callBase)); 

     //Add the method to our class 
     newClass.Members.Add(method); 
    } 

    //TODO: Also add properties if needed? 

    //Make a "CompileUnit" that has a namespace with some 'usings' and then 
    // our new class. 
    var unit = new CodeCompileUnit 
    { 
     Namespaces = 
     { 
      new CodeNamespace(interfaceType.Namespace) 
      { 
       Imports = 
       { 
        new CodeNamespaceImport("System"), 
        new CodeNamespaceImport("System.Linq.Expressions") 
       }, 
       Types = 
       { 
        newClass 
       } 
      } 
     } 
    }; 

    //Use the C# prvider to get a code generator and generate the code 
    //Switch this to VBCodeProvider to generate VB Code 
    var gen = new CSharpCodeProvider().CreateGenerator(); 
    using (var tw = new StringWriter()) 
    { 
     gen.GenerateCodeFromCompileUnit(unit, tw, new CodeGeneratorOptions()); 
     return tw.ToString(); 
    } 
} 

/// <summary> 
/// Helper method for expanding out a type with all it's generic types. 
/// It seems like there should be an easier way to do this but this work. 
/// </summary> 
private static CodeTypeReference MakeTypeReference(Type interfaceType) 
{ 
    //If the Type isn't generic, just wrap is directly 
    if (!interfaceType.IsGenericType) 
     return new CodeTypeReference(interfaceType); 

    //Otherwise wrap it but also pass the generic arguments (recursively calling this method 
    // on all the type arguments. 
    return new CodeTypeReference(interfaceType.Name, 
            interfaceType.GetGenericArguments().Select(MakeTypeReference).ToArray()); 
} 
+1

Câu trả lời rất kỹ lưỡng. Cảm ơn bạn! – Benny

Các vấn đề liên quan