2011-09-15 15 views
6

Sto provando a creare un modello T4 che prenderà le definizioni del metodo in un'interfaccia e riprodurrà la firma e richiamerà un metodo di base con i parametri passati. L'interfaccia definisce una moltitudine di metodi in modo da riscriverli ogni volta che l'interfaccia cambia diventando molto impegnativa. Un'altra complicazione è l'interfaccia è un'interfaccia generica con possibili metodi generici e parametri generici. Finora, l'unico modo in cui riesco a trovare la riproduzione della firma reale (senza le definizioni "1" per i generici) è di ricostruirlo completamente, che diventa molto macchinoso.Firma del metodo effettivo utilizzando Reflection

Nel caso ho una firma come questo nel mio interfaccia:

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

non v'è alcun modo per riprodurre completamente che con la riflessione, senza dover disect i dettagli dell'intera MethodInfo, o c'è un modo rapido per ottenere la stringa sopra in modo da poter scrivere nel mio T4?

Qualsiasi aiuto sarebbe molto apprezzato!

+3

Non una risposta alla tua domanda, ma nella mia esperienza di miscelazione e la riflessione sul T4 assemblee in soluzione finirà in lacrime. VS (che agisce come processore T4) caricherà gli assembly in memoria per riflettere, e quindi otterrà un errore "file in uso" la prossima volta che li costruisci. Suggerimento: trova un altro modo. – Jon

+1

È possibile accedere al codice sorgente dell'interfaccia? Estrarre le informazioni dal testo potrebbe essere più facile che ricostruire la fonte con la riflessione. – dtb

+0

Uno dei motivi per cui un metodo per la produzione delle firme dei metodi non è incluso in .NET Framework è che dovrebbe supportare molte lingue, poiché gli assembly .NET possono essere chiamati da C#, VB.NET, J #, JScript, PowerShell, ecc. e ognuno di essi ha una sintassi diversa per le firme dei metodi. – luksan

risposta

8

Quando ho bisogno di generare codice, guardo spesso allo spazio dei nomi System.CodeDom. Ti consente di creare una rappresentazione logica del codice e quindi ottenere il codice sorgente corrispondente per ciò che hai creato. Tuttavia, non so se posso dire che in questo modo non è anche "ingombrante" come hai detto nella tua risposta (e questo certamente implica la "dissezione" del MethodInfo. Tuttavia, ti dà una base abbastanza decente. passando l'interfaccia che si desidera 'clone', il nome della nuova classe e la classe di base che si desidera estendere questo modo:

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

si tradurrà in questo:

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

anche , puoi modificare alcuni caratteri nel codice e passare al provider VB e otterrai l'output di Visual Basic (forse non utile ma piuttosto interessante):

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

Ecco il GenerateCode bestia. Speriamo che i commenti possono spiegare quello che sta succedendo:

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

Risposta molto accurata. Grazie! – Benny

Problemi correlati