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