2010-10-05 29 views
153

tengo una clase que tiene este aspecto:¿Cómo crear dinámicamente una clase en C#?

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 tiene este aspecto:

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.

+4

¿Es usted desea utilizar esa clase en tiempo de ejecución o sólo generan archivo? –

+0

Quiero que esto se genere en tiempo de ejecución. No quiero un archivo CS físico que resida en mi sistema de archivos. Perdón por no mencionar eso antes. – ashwnacharya

+13

¿Puede darnos una idea aproximada de lo que pretende ** hacer ** con esta clase? – Justin

Respuesta

207

Sí, puede usar el espacio de nombre 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); 
     } 
    } 
} 
+1

¡Impresionante! ¿También puede decirme cómo crear un objeto del tipo devuelto por el método CompileResultType()? – ashwnacharya

+3

Puede usar System.Activator para eso. Actualizaré la respuesta con un ejemplo. – danijels

+3

Tenga en cuenta también que deberá usar la reflexión para examinar, leer y actualizar campos en su tipo dinámico. Si desea intellisense y no hay reflexión, debe tener una clase base o interfaz estática a partir de la cual su clase dinámica hereda y puede convertirse. En ese caso, puede modificar el método GetTypeBuilder() y cambiar la llamada moduleBuilder.DefineType para incluir el tipo estático como el último parámetro (es nulo ahora) – danijels

6

Quiere ver CodeDOM. Permite definir elementos de código y compilarlos. Citando MSDN:

... Este gráfico de objetos se puede representar como código fuente usando un generador de código CodeDOM apoyado por un lenguaje de programación. El CodeDOM también se puede usar para compilar el código fuente en un ensamblaje binario .

+0

Quiero que esto se genere en tiempo de ejecución . No quiero un archivo CS físico que resida en mi sistema de archivos. Perdón por no mencionar eso antes. – ashwnacharya

+0

@ashwnacharya: ** puede ** utilizar CodeDOM para generar el archivo fuente y compilarlo en tiempo de ejecución. – Hemant

+1

Sin embargo, tenga en cuenta que el compilador de CodeDOM toma una cadena sin formato y, por lo tanto, es posible que desee considerar "ataques de inserción de código" similares a los utilizados en XSS y la inyección SQL. – cwap

2

Puede consultar el uso de módulos dinámicos 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 dinámicos coleccionables y, por lo tanto, puede recrear las clases/tipos dinámicamente.

0

Runtime Code Generation with JVM and CLR - Peter Sestoft

trabajo para las personas que están realmente interesados ​​en este tipo de programación.

Mi consejo para usted es que si declara algo intente 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 FiledInfo en lugar de la creación nueva.

+0

El enlace está muerto: -1 –

55

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

Lo que he hecho es:

  • Crear una fuente de C# en una cadena (sin necesidad de escribir en un archivo),
  • ejecutar a través de la Microsoft.CSharp.CSharpCodeProvider (CompileAssemblyFromSource)
  • Encuentra el Tipo generada
  • Y crear una instancia de ese tipo (Activator.CreateInstance)

Este forma en que puede lidiar con 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 (leer: compilador) sobre esa clase que se generará en tiempo de ejecución?

+2

¿Alguna razón en particular para el downvote? –

+12

upvoted ya que no se merece el voto en negativo. – jgauffin

+2

+1: Este puede ser un buen enfoque en las circunstancias correctas. – Ani

13

No sé el uso previsto de tales clases dinámicas, y la generación de código y compilación de tiempo de ejecución se puede hacer, pero requiere un poco de esfuerzo. Tal vez Anonymous Types le ayudaría, algo así como:

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

No puede codificar los nombres de los campos. Les está proporcionando su 'Field.FieldName'. Tener que codificar los nombres de los campos derrota el propósito. Si tienes que hacer eso, también puedes crear la clase. – toddmo

2

Wow! ¡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 "compileresulttype" sub, he cambiado de que:

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 
5

Sobre la base de la respuesta de @ danijels, dinámicamente a crear 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 
0

Usted puede utilizar System.Runtime.Remoting.Proxies.RealProxy. Le permitirá usar el código "normal" en lugar de cosas de tipo ensamblaje de bajo nivel.

ver la respuesta a esta pregunta RealProxy para un buen ejemplo:

How do I intercept a method call in C#?

20

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

public class DynamicClass : DynamicObject 
{ 
    private Dictionary<string, KeyValuePair<Type, object>> _fields; 

    public DynamicClass(List<Field> fields) 
    { 
     _fields = new Dictionary<string, KeyValuePair<Type, object>>(); 
     fields.ForEach(x => _fields.Add(x.FieldName, 
      new KeyValuePair<Type, object>(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, object>(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; 
    } 
} 

almaceno todos los campos de la clase en un diccionario _fields 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 su ejemplo:

var fields = new List<Field>() { 
    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 aquí es cómo 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; 
} 
1

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

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

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

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

Luego usando System.Linq.Dynamic;

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

    Type t = DynamicExpression.CreateClass(props); 

El resto solo utiliza 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); 
} 
1

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 ver https://www.oreilly.com/learning/building-c-objects-dynamically

Cuestiones relacionadas