如何在C#中dynamic创build一个类?

我有一个看起来像这样的课程:

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

而一个对象List<Field>的值为:

 {"EmployeeID","int"}, {"EmployeeName","String"}, {"Designation","String"} 

我想创build一个如下所示的类:

 Class DynamicClass { int EmployeeID, String EmployeeName, String Designation } 

有没有办法做到这一点?

我希望这是在运行时生成的。 我不想要一个物理的CS文件驻留在我的文件系统中。

是的,你可以使用System.Reflection.Emit命名空间。 如果你没有经验,这不是直截了当的,但是这当然是可能的。

编辑:这个代码可能有缺陷,但它会给你一个总的想法,并希望有一个良好的开始实现目标。

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

这将需要一些工作,但肯定不是不可能的。

我所做的是:

  • 在一个string中创build一个C#源代码(不需要写出一个文件),
  • 通过Microsoft.CSharp.CSharpCodeProvider (CompileAssemblyFromSource)运行它
  • find生成的types
  • 并创build该types的实例( Activator.CreateInstance

这样你可以处理你已经知道的C#代码,而不必发出MSIL。

但是,如果你的类实现了一些接口(或者是从一些基类派生的),那么这个效果最好,否则调用代码(read:compiler)如何知道将在运行时生成的类?

您也可以使用DynamicObjectdynamic创build一个类。

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

我将所有类字段与types和值一起存储在字典_fields 。 这两种方法都可以获取或设置一些属性的值。 您必须使用dynamic关键字来创build此类的一个实例。

你的例子的用法:

 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 

编辑:这是如何看起来我的类Field

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

我不知道这些dynamic类的预期用法,可以完成代码生成和运行时编译,但需要一些努力。 也许匿名types可以帮助你,像这样:

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

你想看看CodeDOM 。 它允许定义代码元素并编译它们。 引用MSDN:

…此对象图可以使用CodeDOM代码生成器作为支持的编程语言的源代码呈现。 CodeDOM也可以用来将源代码编译成二进制程序集。

基于@ danijels的回答,在VB.NET中dynamic创build一个类:

 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 

你可以看看使用可以完成这个工作的dynamic模块和类。 唯一的缺点是它仍然在应用程序域中加载。 但是随着.NET框架的使用版本,这可能会改变。 .NET 4.0支持可收集的dynamic程序集,因此您可以dynamic地重新创build类/types。

哇! 谢谢你的回答! 我添加了一些function,以创build一个“datatable到JSON”转换器,我与你分享。

  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 

而在“compileresulttype”子,我改变了:

  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 

您可以使用System.Runtime.Remoting.Proxies.RealProxy。 它将允许您使用“普通”代码而不是低级别的汇编types的东西。

看到这个问题的RealProxy答案就是一个很好的例子:

如何拦截C#中的方法调用?

Runtime Code Generation with JVM and CLR – Peter Sestoft

为那些真正对这种编程感兴趣的人工作。

我的提示是,如果你声明了一些尝试避免string的东西,那么如果你有类字段,最好使用类System.Type来存储字段types而不是string。 为了最好的解决scheme,而不是创build新的类尝试使用已创build的FiledInfo而不是创build新的。

您也可以使用DynamicExpressionsdynamic创build一个类。

由于“字典”有紧凑的初始化器和处理关键的冲突,所以你会想要做这样的事情。

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

或者你可能想要使用JSON转换器来构build你的序列化的string对象到可pipe理的。

然后使用System.Linq.Dynamic;

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

剩下的就是使用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); }