J'ai une classe qui ressemble à ceci:
public class Field
{
public string FieldName;
public string FieldType;
}
Et un objet List<Field>
avec des valeurs:
{"EmployeeID","int"},
{"EmployeeName","String"},
{"Designation","String"}
Je veux créer une classe qui ressemble à ceci:
Class DynamicClass
{
int EmployeeID,
String EmployeeName,
String Designation
}
Est-ce qu'il y a un moyen de faire ça?
Je veux que cela soit généré au moment de l'exécution. Je ne souhaite pas qu'un fichier CS physique réside dans mon système de fichiers.
Oui, vous pouvez utiliser System.Reflection.Emit
namespace pour cela. Ce n’est pas évident si vous n’avez pas d’expérience, mais c’est certainement possible.
Edit: Ce code peut être imparfait, mais il vous donnera l’idée générale et nous espérons un bon départ vers l’objectif.
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);
}
}
}
Cela demandera du travail, mais ce n’est certainement pas impossible.
Ce que j'ai fait c'est:
Microsoft.CSharp.CSharpCodeProvider
(CompileAssemblyFromSource)Activator.CreateInstance
)De cette façon, vous pouvez gérer le code C # que vous connaissez déjà, au lieu d’avoir à émettre MSIL.
Mais cela fonctionne mieux si votre classe implémente une interface (ou est dérivée d'une classe de base), sinon comment le code appelant (read: compiler) peut-il connaître la classe qui sera générée au moment de l'exécution?
Vous pouvez également créer dynamiquement une classe en utilisant 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;
}
}
Je stocke tous les champs de classe dans un dictionnaire _fields
ainsi que leurs types et leurs valeurs. Les deux méthodes doivent obtenir ou définir une valeur pour certaines propriétés. Vous devez utiliser le mot clé dynamic
pour créer une instance de cette classe.
L'utilisation avec votre exemple:
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
Edit: Et voici à quoi ressemble ma classe Field
:
public class Field
{
public Field(string name, Type type)
{
this.FieldName = name;
this.FieldType = type;
}
public string FieldName;
public Type FieldType;
}
Je ne connais pas l'utilisation prévue de ces classes dynamiques. La génération de code et la compilation au moment de l'exécution peuvent être effectuées, mais cela demande quelques efforts. Peut-être que Types anonymes pourrait vous aider, quelque chose comme:
var v = new { EmployeeID = 108, EmployeeName = "John Doe" };
Je sais que je rouvre cette vieille tâche, mais avec la version C 6.0, cette tâche est absolument indolore.
dynamic expando = new ExpandoObject();
expando.EmployeeID=42;
expando.Designation="unknown";
expando.EmployeeName="curt"
//or more dynamic
AddProperty(expando, "Language", "English");
pour plus, voyez https://www.oreilly.com/learning/building-c-objects-dynamically
Vous voulez regarder CodeDOM . Cela permet de définir des éléments de code et de les compiler. Citant MSDN:
... Ce graphique d'objet peut être rendu sous forme de code source à l'aide d'un générateur de code CodeDOM pour un langage de programmation pris en charge. CodeDOM peut également être utilisé pour compiler le code source dans un assemblage binaire.
Sur la base de la réponse de @ danijels, créez dynamiquement une classe dans 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
Vous pouvez également créer dynamiquement une classe en utilisant DynamicExpressions .
Puisque les dictionnaires ont des initialiseurs compacts et gèrent les conflits de clés, vous voudrez faire quelque chose comme ça.
var list = new Dictionary<string, string> {
{
"EmployeeID",
"int"
}, {
"EmployeeName",
"String"
}, {
"Birthday",
"DateTime"
}
};
Vous pouvez également utiliser un convertisseur JSON pour construire votre objet chaîne sérialisé en quelque chose de gérable.
Ensuite, en utilisant System.Linq.Dynamic;
IEnumerable<DynamicProperty> props = list.Select(property => new DynamicProperty(property.Key, Type.GetType(property.Value))).ToList();
Type t = DynamicExpression.CreateClass(props);
Le reste utilise simplement 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);
}
Hou la la! Merci pour cette réponse! J'ai ajouté quelques fonctionnalités pour créer un convertisseur "datatable to json" que je partage avec vous.
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
Et dans "compileresulttype", j'ai changé cela:
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
Vous pouvez utiliser des modules dynamiques et des classes pouvant faire le travail. Le seul inconvénient est qu'il reste chargé dans le domaine d'application. Mais avec la version du framework .NET utilisée, cela pourrait changer. .NET 4.0 prend en charge les assemblys dynamiques à collectionner et vous permet donc de recréer les classes/types de manière dynamique.
Pour ceux qui souhaitent créer une classe dynamique, il suffit de créer des propriétés (par exemple, POCO) et de créer une liste de cette classe. En utilisant le code fourni plus tard, cela va créer une classe dynamique et en créer une liste.
var properties = new List<DynamicTypeProperty>()
{
new DynamicTypeProperty("doubleProperty", typeof(double)),
new DynamicTypeProperty("stringProperty", typeof(string))
};
// create the new type
var dynamicType = DynamicType.CreateDynamicType(properties);
// create a list of the new type
var dynamicList = DynamicType.CreateDynamicList(dynamicType);
// get an action that will add to the list
var addAction = DynamicType.GetAddAction(dynamicList);
// call the action, with an object[] containing parameters in exact order added
addAction.Invoke(new object[] {1.1, "item1"});
addAction.Invoke(new object[] {2.1, "item2"});
addAction.Invoke(new object[] {3.1, "item3"});
Voici les classes utilisées par le code précédent.
Remarque: Vous devrez également faire référence à la bibliothèque Microsoft.CodeAnalysis.CSharp.
/// <summary>
/// A property name, and type used to generate a property in the dynamic class.
/// </summary>
public class DynamicTypeProperty
{
public DynamicTypeProperty(string name, Type type)
{
Name = name;
Type = type;
}
public string Name { get; set; }
public Type Type { get; set; }
}
public static class DynamicType
{
/// <summary>
/// Creates a list of the specified type
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static IEnumerable<object> CreateDynamicList(Type type)
{
var listType = typeof(List<>);
var dynamicListType = listType.MakeGenericType(type);
return (IEnumerable<object>) Activator.CreateInstance(dynamicListType);
}
/// <summary>
/// creates an action which can be used to add items to the list
/// </summary>
/// <param name="listType"></param>
/// <returns></returns>
public static Action<object[]> GetAddAction(IEnumerable<object> list)
{
var listType = list.GetType();
var addMethod = listType.GetMethod("Add");
var itemType = listType.GenericTypeArguments[0];
var itemProperties = itemType.GetProperties();
var action = new Action<object[]>((values) =>
{
var item = Activator.CreateInstance(itemType);
for(var i = 0; i < values.Length; i++)
{
itemProperties[i].SetValue(item, values[i]);
}
addMethod.Invoke(list, new []{item});
});
return action;
}
/// <summary>
/// Creates a type based on the property/type values specified in the properties
/// </summary>
/// <param name="properties"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static Type CreateDynamicType(IEnumerable<DynamicTypeProperty> properties)
{
StringBuilder classCode = new StringBuilder();
// Generate the class code
classCode.AppendLine("using System;");
classCode.AppendLine("namespace Dexih {");
classCode.AppendLine("public class DynamicClass {");
foreach (var property in properties)
{
classCode.AppendLine($"public {property.Type.Name} {property.Name} {{get; set; }}");
}
classCode.AppendLine("}");
classCode.AppendLine("}");
var syntaxTree = CSharpSyntaxTree.ParseText(classCode.ToString());
var references = new MetadataReference[]
{
MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(DictionaryBase).GetTypeInfo().Assembly.Location)
};
var compilation = CSharpCompilation.Create("DynamicClass" + Guid.NewGuid() + ".dll",
syntaxTrees: new[] {syntaxTree},
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using (var ms = new MemoryStream())
{
var result = compilation.Emit(ms);
if (!result.Success)
{
var failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error);
var message = new StringBuilder();
foreach (var diagnostic in failures)
{
message.AppendFormat("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
}
throw new Exception($"Invalid property definition: {message}.");
}
else
{
ms.Seek(0, SeekOrigin.Begin);
var Assembly = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(ms);
var dynamicType = Assembly.GetType("Dexih.DynamicClass");
return dynamicType;
}
}
}
}
Vous pouvez utiliser System.Runtime.Remoting.Proxies.RealProxy. Cela vous permettra d'utiliser du code "normal" plutôt que des éléments de type d'assemblage de bas niveau.
Voir la réponse de RealProxy à cette question pour un bon exemple:
Runtime Code Generation with JVM and CLR
- Peter Sestoft
Travaillez pour des personnes vraiment intéressées par ce type de programmation.
Mon conseil pour vous est que si vous déclarez quelque chose, essayez d'éviter la chaîne. Par conséquent, si vous avez la classe Field, il est préférable d'utiliser la classe System.Type pour stocker le type de champ plutôt qu'une chaîne. Et pour des raisons de meilleures solutions au lieu de créer de nouvelles classes, essayez d'utiliser celles qui ont été créées FiledInfo au lieu de créer de nouvelles.