2011-09-15 12 views
6

Estoy tratando de construir una plantilla T4 que tome las definiciones del método en una interfaz y reproduzca la firma y llame a un método base con los parámetros pasados. La interfaz define una multitud de métodos, por lo que volver a escribirlos cada vez que cambia la interfaz se convierte en un gran desafío. Otra complicación es que la interfaz es una interfaz genérica con posibles métodos genéricos y parámetros genéricos. Hasta ahora, la única forma en que puedo encontrar para reproducir la firma real (sin las definiciones "1" para los genéricos) es reconstruirla por completo, lo que se vuelve muy engorroso.Firma del método real usando Reflection

En el caso de que tenga una firma como esta en mi interfaz:

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

¿Hay alguna manera de reproducir por completo que con la reflexión sin tener que diseccionar todos los detalles del MethodInfo, o hay una forma rápida de obtener la secuencia de arriba para que pueda escribirla en mi T4?

¡Cualquier ayuda sería muy apreciada!

+3

una respuesta a su pregunta, pero en mi experiencia de mezcla de T4 y la reflexión sobre asambleas en su solución terminará en lágrimas. VS (que actúa como procesador T4) cargará los ensamblajes en la memoria para que se reflejen, y luego recibirá un error de "archivo en uso" la próxima vez que los construya. Sugerencia: encuentra otra forma. – Jon

+1

¿Puede acceder al código fuente de la interfaz? Extraer la información del texto podría ser más fácil que reconstruir la fuente con el reflejo. – dtb

+0

Una razón por la cual un método para producir firmas de métodos no está incluido en .NET Framework es que debería admitir muchos idiomas, ya que los ensamblados de .NET se pueden invocar desde C#, VB.NET, J #, JScript, PowerShell, etc. , y cada uno tiene una sintaxis diferente para sus firmas de métodos. – luksan

Respuesta

8

Cuando necesito generar código, a menudo miro al espacio de nombre System.CodeDom. Le permite crear una representación lógica de código y luego obtener el código fuente correspondiente para lo que ha creado. Sin embargo, no sé si puedo decir que esta forma tampoco es "engorrosa" como dijiste en tu respuesta (y esto ciertamente involucra 'diseccionar' el MethodInfo. Sin embargo, te da una base bastante decente. pasando en la interfaz que desea 'clon', el nombre de la nueva clase y la clase base que desea ampliar de este modo:

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

dará lugar a esto:

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

también , puede cambiar algunos caracteres en el código y cambiar al proveedor de VB y obtendrá una salida de Visual Basic (tal vez no útil, pero un poco cool):

'------------------------------------------------------------------------------ 
' <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 

Aquí es el GenerateCode bestia. Espero que los comentarios pueden explicar lo que está pasando:

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

Respuesta muy completa. ¡Gracias! – Benny

Cuestiones relacionadas