objetos - listas en c#
¿Cómo crear dinámicamente una clase en C#? (12)
Tengo una clase que se ve así:
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 un archivo CS físico que resida en mi sistema de archivos.
¡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
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
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 los tipos anónimos te ayuden, algo así como:
var v = new { EmployeeID = 108, EmployeeName = "John Doe" };
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:
Puede ver 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.
Quieres mirar CodeDOM . Permite definir elementos de código y compilarlos. 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 programación compatible. CodeDOM también se puede usar para compilar código fuente en un ensamblaje binario.
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
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);
}
}
}
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<string, string> {
{
"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<DynamicProperty> props = list.Select(property => new DynamicProperty(property.Key, Type.GetType(property.Value))).ToList();
Type t = DynamicExpression.CreateClass(props);
El resto 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);
}
También puede crear dinámicamente una clase utilizando 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;
}
}
_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<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 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;
}
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?
Runtime Code Generation with JVM and CLR
- Peter Sestoft
Trabajar para personas que estén realmente interesadas en este tipo de programació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 lugar de FiledInfo nuevas.