2010-10-05 69 views
153

我有一个类,它看起来像这样:如何在C#中动态创建一个类?

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

和对象List<Field>与价值观:

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

我想创建一个类,看起来像这样:

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

有没有办法做到这一点?

我希望它在运行时生成。我不希望物理CS文件驻留在我的文件系统中。

+4

你想使用这个类在运行时或仅生成文件? – 2010-10-05 09:05:56

+0

我希望这是在运行时生成的。我不想要一个物理CS文件驻留在我的文件系统中。对不起,先不提。 – ashwnacharya 2010-10-05 09:12:13

+13

你可以给我们一个粗略的想法,说明你打算在这门课上做什么**。 – Justin 2010-10-05 09:17:11

回答

207

是的,你可以使用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); 
     } 
    } 
} 
+1

真棒!你能告诉我如何创建一个由CompileResultType()方法返回的类型的对象吗? – ashwnacharya 2010-10-05 09:33:52

+3

您可以使用System.Activator。我会用一个例子来更新答案。 – danijels 2010-10-05 09:41:36

+3

另请注意,您将不得不使用反射来检查,读取和更新动态类型中的字段。如果你想要intellisense并且没有反射,你必须有一个静态的基类或接口,动态类继承并且可以被转换。在这种情况下,您可以修改GetTypeBuilder()方法并更改moduleBuilder.DefineType调用以包含静态类型作为最后一个参数(现在为null) – danijels 2010-10-05 09:49:40

6

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

...这个对象图可以呈现为使用的CodeDOM代码 发生器支持的编程语言 源代码。 CodeDOM也可用于 将源代码编译为二进制程序集 。

+0

我希望这是在运行时生成。我不想要一个物理CS文件驻留在我的文件系统中。对不起,先不提。 – ashwnacharya 2010-10-05 09:11:47

+0

@ashwnacharya:您可**使用CodeDOM生成源文件并在运行时编译它! – Hemant 2010-10-05 09:15:15

+1

请注意,CodeDOM编译器需要原始字符串,因此您可能需要考虑类似于XSS和SQL注入中使用的“代码插入攻击”。 – cwap 2010-10-05 09:33:53

2

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

0

Runtime Code Generation with JVM and CLR - 彼得·塞斯托夫特

工作是在这种类型的节目很感兴趣的人。

我给你的提示是,如果你声明了一些尝试避免字符串的东西,那么如果你有class字段,最好使用类System.Type来存储字段类型而不是字符串。 为了最好的解决方案而不是创建新的类,尝试使用已创建的FiledInfo而不是创建新的类。

+0

链接已死:-1 – 2017-09-30 21:23:06

55

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

我所做的是:

  • 在一个字符串(不需要写出来的文件)创建一个C#源,通过
  • 运行它Microsoft.CSharp.CSharpCodeProvider(CompileAssemblyFromSource)
  • 找到生成的类型
  • ,并创建一个类型(Activator.CreateInstance

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

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

+2

downvote的任何特定原因? – 2010-10-05 10:35:20

+12

upvoted,因为你不值得downvote。 – jgauffin 2010-10-05 10:37:18

+2

+1:在适当的情况下,这可能是一个好方法。 – Ani 2010-10-05 10:51:09

13

我不知道这些动态类的预期用法,可以完成代码生成和运行时编译,但需要一些努力。 也许Anonymous Types会帮助你,是这样的:

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

你不能硬编码的字段名称。他为他们提供了他的'Field.FieldName'。必须对字段名称进行硬编码才能达到目的。如果你必须这样做,你不妨创建这个班。 – toddmo 2016-11-30 23:37:35

2

哇!谢谢你的回答!我添加了一些功能来创建一个我可以与你分享的“datatable to 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 
5

基于@ danijels的回答,动态地创建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

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

见RealProxy回答这个问题的一个很好的例子:

How do I intercept a method call in C#?

20

您也可以动态使用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中。这两种方法都可以获取或设置一些属性的值。您必须使用dynamic关键字来创建此类的一个实例。

你的榜样用法:

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

您也可以动态使用DynamicExpressions创建一个类。

由于'字典'具有紧凑的初始化器并处理关键冲突,所以您会想要做这样的事情。

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

或者您可能想要使用JSON转换器来将您的序列化字符串对象构建为可管理的东西。

然后使用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); 
}