¿Cómo crear dinámicamente una clase en C #?

Tengo una clase que se ve así:

public class Field { public string FieldName; public string FieldType; } 

Y un objeto List 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 un archivo CS físico que resida en mi sistema de archivos.

Sí, puede usar el System.Reflection.Emit nombres System.Reflection.Emit para esto. No es sencillo si no tienes experiencia con él, pero ciertamente es posible.

Editar: Este código puede ser defectuoso, pero le dará la idea general y, con suerte, un buen comienzo hacia la meta.

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

Tomará algún trabajo, pero ciertamente no es imposible.

Lo que he hecho es:

  • Crea una fuente de C # en una cadena (no es necesario escribir en un archivo),
  • Ejecútelo a través del Microsoft.CSharp.CSharpCodeProvider (CompileAssemblyFromSource)
  • Encuentra el tipo generado
  • Y crea una instancia de ese tipo ( Activator.CreateInstance )

De esta forma, 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 sabe el código de llamada (read: compiler) sobre esa clase que se generará en tiempo de ejecución?

También puede crear dinámicamente una clase utilizando DynamicObject .

 public class DynamicClass : DynamicObject { private Dictionary> _fields; public DynamicClass(List fields) { _fields = new Dictionary>(); fields.ForEach(x => _fields.Add(x.FieldName, new KeyValuePair(x.FieldType, null))); } public override bool TrySetMember(SetMemberBinder binder, object value) { if (_fields.ContainsKey(binder.Name)) { var type = _fields[binder.Name].Key; if (value.GetType() == type) { _fields[binder.Name] = new KeyValuePair(type, value); return true; } else throw new Exception("Value " + value + " is not of type " + type.Name); } return false; } public override bool TryGetMember(GetMemberBinder binder, out object result) { result = _fields[binder.Name].Value; return true; } } 

_fields todos los campos de clase en un _fields campo de diccionario junto con sus tipos y valores. Ambos métodos son para obtener o establecer valor para algunas de las propiedades. Debe usar la palabra clave dynamic para crear una instancia de esta clase.

El uso con tu ejemplo:

 var fields = new List() { new Field("EmployeeID", typeof(int)), new Field("EmployeeName", typeof(string)), new Field("Designation", typeof(string)) }; dynamic obj = new DynamicClass(fields); //set obj.EmployeeID = 123456; obj.EmployeeName = "John"; obj.Designation = "Tech Lead"; obj.Age = 25; //Exception: DynamicClass does not contain a definition for 'Age' obj.EmployeeName = 666; //Exception: Value 666 is not of type String //get Console.WriteLine(obj.EmployeeID); //123456 Console.WriteLine(obj.EmployeeName); //John Console.WriteLine(obj.Designation); //Tech Lead 

Editar: Y así es como se ve mi clase Field :

 public class Field { public Field(string name, Type type) { this.FieldName = name; this.FieldType = type; } public string FieldName; public Type FieldType; } 

No sé el uso previsto de tales clases dinámicas, y la generación de código y comstackción de tiempo de ejecución se puede hacer, pero requiere un poco de esfuerzo. Tal vez los tipos anónimos te ayuden, algo así como:

 var v = new { EmployeeID = 108, EmployeeName = "John Doe" }; 

Quieres mirar CodeDOM . Permite definir elementos de código y comstackrlos. Citando MSDN:

… Este gráfico de objetos se puede representar como código fuente utilizando un generador de códigos CodeDOM para un lenguaje de progtwigción compatible. CodeDOM también se puede usar para comstackr código fuente en un ensamblaje binario.

Basado en la respuesta de @danijels, crea dinámicamente una clase en VB.NET:

 Imports System.Reflection Imports System.Reflection.Emit Public Class ObjectBuilder Public Property myType As Object Public Property myObject As Object Public Sub New(fields As List(Of Field)) myType = CompileResultType(fields) myObject = Activator.CreateInstance(myType) End Sub Public Shared Function CompileResultType(fields As List(Of Field)) As Type Dim tb As TypeBuilder = GetTypeBuilder() Dim constructor As ConstructorBuilder = tb.DefineDefaultConstructor(MethodAttributes.[Public] Or MethodAttributes.SpecialName Or MethodAttributes.RTSpecialName) For Each field In fields CreateProperty(tb, field.Name, field.Type) Next Dim objectType As Type = tb.CreateType() Return objectType End Function Private Shared Function GetTypeBuilder() As TypeBuilder Dim typeSignature = "MyDynamicType" Dim an = New AssemblyName(typeSignature) Dim assemblyBuilder As AssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run) Dim moduleBuilder As ModuleBuilder = assemblyBuilder.DefineDynamicModule("MainModule") Dim tb As TypeBuilder = moduleBuilder.DefineType(typeSignature, TypeAttributes.[Public] Or TypeAttributes.[Class] Or TypeAttributes.AutoClass Or TypeAttributes.AnsiClass Or TypeAttributes.BeforeFieldInit Or TypeAttributes.AutoLayout, Nothing) Return tb End Function Private Shared Sub CreateProperty(tb As TypeBuilder, propertyName As String, propertyType As Type) Dim fieldBuilder As FieldBuilder = tb.DefineField("_" & propertyName, propertyType, FieldAttributes.[Private]) Dim propertyBuilder As PropertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, Nothing) Dim getPropMthdBldr As MethodBuilder = tb.DefineMethod("get_" & propertyName, MethodAttributes.[Public] Or MethodAttributes.SpecialName Or MethodAttributes.HideBySig, propertyType, Type.EmptyTypes) Dim getIl As ILGenerator = getPropMthdBldr.GetILGenerator() getIl.Emit(OpCodes.Ldarg_0) getIl.Emit(OpCodes.Ldfld, fieldBuilder) getIl.Emit(OpCodes.Ret) Dim setPropMthdBldr As MethodBuilder = tb.DefineMethod("set_" & propertyName, MethodAttributes.[Public] Or MethodAttributes.SpecialName Or MethodAttributes.HideBySig, Nothing, {propertyType}) Dim setIl As ILGenerator = setPropMthdBldr.GetILGenerator() Dim modifyProperty As Label = setIl.DefineLabel() Dim exitSet As Label = 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) End Sub End Class 

También puede crear dinámicamente una clase utilizando DynamicExpressions .

Dado que ‘Dictionary tiene inicializadores compactos y maneja las colisiones de teclas, querrás hacer algo como esto.

  var list = new Dictionary { { "EmployeeID", "int" }, { "EmployeeName", "String" }, { "Birthday", "DateTime" } }; 

O puede usar un convertidor JSON para construir su objeto serial serializado en algo manejable.

Luego usando System.Linq.Dynamic;

  IEnumerable props = list.Select(property => new DynamicProperty(property.Key, Type.GetType(property.Value))).ToList(); Type t = DynamicExpression.CreateClass(props); 

El rest solo usa System.Reflection.

  object obj = Activator.CreateInstance(t); t.GetProperty("EmployeeID").SetValue(obj, 34, null); t.GetProperty("EmployeeName").SetValue(obj, "Albert", null); t.GetProperty("Birthday").SetValue(obj, new DateTime(1976, 3, 14), null); } 

Sé que vuelvo a abrir esta tarea anterior, pero con c # 6.0 esta tarea es absolutamente indolora.

 dynamic expando = new ExpandoObject(); expando.EmployeeID=42; expando.Designation="unknown"; expando.EmployeeName="curt" //or more dynamic AddProperty(expando, "Language", "English"); 

para más información, consulte https://www.oreilly.com/learning/building-c-objects-dynamically

Puede ver el uso de módulos dynamics y clases que pueden hacer el trabajo. La única desventaja es que permanece cargado en el dominio de la aplicación. Pero con la versión de .NET framework en uso, eso podría cambiar. .NET 4.0 admite ensamblajes dynamics coleccionables y, por lo tanto, puede recrear las clases / tipos dinámicamente.

¡Guauu! ¡Gracias por esa respuesta! Agregué algunas características para crear un convertidor “datatable to json” que comparto contigo.

  Public Shared Sub dt2json(ByVal _dt As DataTable, ByVal _sb As StringBuilder) Dim t As System.Type Dim oList(_dt.Rows.Count - 1) As Object Dim jss As New JavaScriptSerializer() Dim i As Integer = 0 t = CompileResultType(_dt) For Each dr As DataRow In _dt.Rows Dim o As Object = Activator.CreateInstance(t) For Each col As DataColumn In _dt.Columns setvalue(o, col.ColumnName, dr.Item(col.ColumnName)) Next oList(i) = o i += 1 Next jss = New JavaScriptSerializer() jss.Serialize(oList, _sb) End Sub 

Y en el sub “compileresulttype”, cambié eso:

  For Each column As DataColumn In _dt.Columns CreateProperty(tb, column.ColumnName, column.DataType) Next Private Shared Sub setvalue(ByVal _obj As Object, ByVal _propName As String, ByVal _propValue As Object) Dim pi As PropertyInfo pi = _obj.GetType.GetProperty(_propName) If pi IsNot Nothing AndAlso pi.CanWrite Then If _propValue IsNot DBNull.Value Then pi.SetValue(_obj, _propValue, Nothing) Else Select Case pi.PropertyType.ToString Case "System.String" pi.SetValue(_obj, String.Empty, Nothing) Case Else 'let the serialiser use javascript "null" value. End Select End If End If End Sub 

Puede usar System.Runtime.Remoting.Proxies.RealProxy. Le permitirá usar el código “normal” en lugar de cosas de tipo ensamblaje de bajo nivel.

Vea la respuesta de RealProxy a esta pregunta para un buen ejemplo:

¿Cómo intercepto una llamada a método en C #?

Runtime Code Generation with JVM and CLR – Peter Sestoft

Trabajar para personas que estén realmente interesadas en este tipo de progtwigción.

Mi consejo para usted es que si declara algo trate de evitar la cadena, por lo que si tiene campo de clase, es mejor usar la clase System.Type para almacenar el tipo de campo que una cadena. Y por el bien de las mejores soluciones en lugar de la creación, las nuevas clases intentan usar aquellas que se han creado en FiledInfo en lugar de crearlas nuevas.