2010-10-05 27 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); 
}