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());
}
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
È possibile accedere al codice sorgente dell'interfaccia? Estrarre le informazioni dal testo potrebbe essere più facile che ricostruire la fonte con la riflessione. – dtb
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