¿Cómo crear dinámicamente una clase?
Tengo una clase que se parece a esta:
public class Field
{
public string FieldName;
public string FieldType;
}
Y un objeto List<Field>
con valores:
{"EmployeeID","int"},
{"EmployeeName","String"},
{"Designation","String"}
Quiero crear una clase que se vea así:
Class DynamicClass
{
int EmployeeID,
String EmployeeName,
String Designation
}
¿Hay alguna manera de hacer esto?
Quiero que esto se genere en tiempo de ejecución. No quiero que un archivo CS físico resida en mi sistema de archivos.
Sí, puedes usar System.Reflection.Emit
el espacio de nombres para esto. No es sencillo si no tienes experiencia con ello, pero ciertamente es posible.
Editar: este código puede tener errores, pero le dará una idea general y, con suerte, tendrá un buen comienzo hacia el objetivo.
using System;
using System.Reflection;
using System.Reflection.Emit;
namespace TypeBuilderNamespace
{
public static class MyTypeBuilder
{
public static void CreateNewObject()
{
var myType = CompileResultType();
var myObject = Activator.CreateInstance(myType);
}
public static Type CompileResultType()
{
TypeBuilder tb = GetTypeBuilder();
ConstructorBuilder constructor = tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);
// NOTE: assuming your list contains Field objects with fields FieldName(string) and FieldType(Type)
foreach (var field in yourListOfFields)
CreateProperty(tb, field.FieldName, field.FieldType);
Type objectType = tb.CreateType();
return objectType;
}
private static TypeBuilder GetTypeBuilder()
{
var typeSignature = "MyDynamicType";
var an = new AssemblyName(typeSignature);
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
TypeBuilder tb = moduleBuilder.DefineType(typeSignature,
TypeAttributes.Public |
TypeAttributes.Class |
TypeAttributes.AutoClass |
TypeAttributes.AnsiClass |
TypeAttributes.BeforeFieldInit |
TypeAttributes.AutoLayout,
null);
return tb;
}
private static void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType)
{
FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
MethodBuilder getPropMthdBldr = tb.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
ILGenerator getIl = getPropMthdBldr.GetILGenerator();
getIl.Emit(OpCodes.Ldarg_0);
getIl.Emit(OpCodes.Ldfld, fieldBuilder);
getIl.Emit(OpCodes.Ret);
MethodBuilder setPropMthdBldr =
tb.DefineMethod("set_" + propertyName,
MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig,
null, new[] { propertyType });
ILGenerator setIl = setPropMthdBldr.GetILGenerator();
Label modifyProperty = setIl.DefineLabel();
Label exitSet = setIl.DefineLabel();
setIl.MarkLabel(modifyProperty);
setIl.Emit(OpCodes.Ldarg_0);
setIl.Emit(OpCodes.Ldarg_1);
setIl.Emit(OpCodes.Stfld, fieldBuilder);
setIl.Emit(OpCodes.Nop);
setIl.MarkLabel(exitSet);
setIl.Emit(OpCodes.Ret);
propertyBuilder.SetGetMethod(getPropMthdBldr);
propertyBuilder.SetSetMethod(setPropMthdBldr);
}
}
}
Requerirá algo de trabajo, pero ciertamente no es imposible.
Lo que he hecho es:
- Cree una fuente C# en una cadena (no es necesario escribirla en un archivo),
- Ejecútelo a través de
Microsoft.CSharp.CSharpCodeProvider
(CompileAssemblyFromSource) - Encuentra el tipo generado
- Y crea una instancia de ese Tipo (
Activator.CreateInstance
)
De esta manera puede manejar el código C# que ya conoce, en lugar de tener que emitir MSIL.
Pero esto funciona mejor si su clase implementa alguna interfaz (o se deriva de alguna clase base); de lo contrario, ¿cómo puede el código de llamada (léase: compilador) saber acerca de esa clase que se generará en tiempo de ejecución?