在執行時生成C# .NET類
本文譯自:
Generating C# .NET Classes at Runtime
作者:WedPort
在我的C#職業生涯中,有幾次我不得不在執行時生成新的型別。希望把它寫下來能幫助有相同應用需求的人。這也意味著我以後不必在查詢相同問題的StackOverflow文章了。我最初是在.NET 4.6.2中這樣做的,但我已經更新到為.NET Core 3.0提供了示例。所有程式碼都可以在我的GitHub上面找到。
GitHub:
為什麼我需要在執行時生成類?
在執行時生產新型別的需求通常是由於執行時才知道類屬性,滿足效能要求以及需要在新型別中新增功能。當你嘗試這樣做的時候,你應該考慮的第一件事是:這是否真的是一個明智的解決方案。在深入思考之前,還有很多其他事情可以嘗試,問你自己這樣的問題:
- 我可以使用普通的類嗎
- 我可以使用Dictionary、Tuple或者物件陣列(Array)?
- 我是否可以使用擴充套件物件
- 我確定我不能使用一個普通的類嗎?
如果你認為這仍然是必要的,請繼續閱讀下面的內容。
示例用例
作為一名開發人員,我將大量資料繫結到各種WPF Grids中。大多數時候屬性是固定的,我可以使用預定義的類。有時候,我不得不動態的構建網格,並且能夠在應用程式執行時更改資料。採取以下顯示ID和一些財務資料的類(FTSE和CAC是指數,其屬性代表指數價格):
public class PriceHolderViewModel : ViewModelBase{ public long Id { get; set; } public decimal FTSE100 { get; set; } public decimal CAC40 { get; set; } }
如果我們僅對其中的屬性感興趣,該類定義的非常棒。但是,如果要使用更多屬性擴充套件此類,則需要在程式碼中新增它,重新編譯並在新版本中進行部署。
相反的,我們可以做的是跟蹤物件所需的屬性,並在執行時構建類。這將允許我們在需要是不斷的新增和刪除屬性,並使用反射來更新它們的值。
// Keep track of my propertiesvar _properties = new Dictionary<string, Type>(new[]{ new KeyValuePair<string, Type>( "FTSE100", typeof(Decimal) ), new KeyValuePair<string, Type>( "CAC40", typeof(Decimal) ) });
建立你的型別
下面的示例向您展示瞭如何在執行時構建新型別。你需要使用
**System.Reflection.Emit**
庫來構造一個新的動態程式集,您的類將在其中建立,然後是模組和型別。與舊的
** .NET Framework**
框架不同,在舊的版本中,你需要在當前程式的
AppDomain
中建立程式集 ,而在
** .NET Core**
中,
AppDomain
不再可用。你將看到我使用GUID建立了一個新型別名稱,以便於跟蹤型別的版本。在以前,你不能建立具有相同名稱的兩個型別,但是現在似乎不是這樣了。
public Type GeneratedType { private set; get; }private void Initialise(){ var newTypeName = Guid.NewGuid().ToString(); var assemblyName = new AssemblyName(newTypeName); var dynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); var dynamicModule = dynamicAssembly.DefineDynamicModule("Main"); var dynamicType = dynamicModule.DefineType(newTypeName, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout, typeof(T)); // This is the type of class to derive from. Use null if there isn't one dynamicType.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName); foreach (var property in Properties) AddProperty(dynamicType, property.Key, property.Value); GeneratedType = dynamicType.CreateType(); }
在定義型別時,你可以提供一種型別,從中派生新的型別。如果你的基類具有要包含在新型別中的某些功能或屬性,這將非常有用。之前,我曾使用它在執行時擴充套件
ViewModel
和
Serializable
型別。
在你建立了
TypeBuilder
後,你可以使用下面提供的程式碼開始新增屬性。它建立了支援欄位和所需的中間語言,以便透過
Getter
和
Setter
訪問它們。為每個屬性完成此操作後,可以使用
CreateType()
建立型別的例項。
private static void AddProperty(TypeBuilder typeBuilder, string propertyName, Type propertyType){ var fieldBuilder = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private); var propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null); var getMethod = typeBuilder.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes); var getMethodIL = getMethod.GetILGenerator(); getMethodIL.Emit(OpCodes.Ldarg_0); getMethodIL.Emit(OpCodes.Ldfld, fieldBuilder); getMethodIL.Emit(OpCodes.Ret); var setMethod = typeBuilder.DefineMethod("set_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, null, new[] { propertyType }); var setMethodIL = setMethod.GetILGenerator(); Label modifyProperty = setMethodIL.DefineLabel(); Label exitSet = setMethodIL.DefineLabel(); setMethodIL.MarkLabel(modifyProperty); setMethodIL.Emit(OpCodes.Ldarg_0); setMethodIL.Emit(OpCodes.Ldarg_1); setMethodIL.Emit(OpCodes.Stfld, fieldBuilder); setMethodIL.Emit(OpCodes.Nop); setMethodIL.MarkLabel(exitSet); setMethodIL.Emit(OpCodes.Ret); propertyBuilder.SetGetMethod(getMethod); propertyBuilder.SetSetMethod(setMethod); }
有了型別後,就很容易透過使用
Activator.CreateInstance()
來建立它的例項。但是,你希望能夠更改已建立的屬性的值,為了做到這一點,你可以再次使用反射來獲取
propertyInfos
並提取Set方法。一旦有了這些屬性,電影它們類設定屬性值就相對簡單了。
foreach (var property in Properties) { var propertyInfo = GeneratedType.GetProperty(property.Key); var setMethod = propertyInfo.GetSetMethod(); setMethod.Invoke(objectInstance, new[] { propertyValue }); }
現在,您可以在執行時使用自定義屬性來建立自己的型別,並具有更新其值的功能,一切就緒。 我發現的唯一障礙是建立一個可以儲存新型別例項的列表。 WPF中的DataGrid傾向於只讀取List的常規引數型別的屬性。 這意味著即使您使用新屬性擴充套件了基類,使用AutoGenerateProperties也只能看到基類中的屬性。 解決方案是使用生成的型別顯式建立一個新的List。 我在下面提供瞭如何執行此操作的示例:
var listGenericType = typeof(List<>);var list = listGenericType.MakeGenericType(GeneratedType);var constructor = list.GetConstructor(new Type[] { });var newList = (IList)constructor.Invoke(new object[] { });foreach (var value in values) newList.Add(value);
結論
我已經在GitHub中建立了一個示例應用程式。它包含一個UI來幫助您除錯和理解執行時新型別的建立,以及如何更新值。如果您有任何問題或意見,請隨時與我們聯絡。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69977734/viewspace-2700228/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 在 Ubuntu 上安裝 .NET SDK 或 .NET 執行時Ubuntu
- Delphi程式執行時實時生成報表
- 造成類在多執行緒時不安全的原因執行緒
- C# 生成RSS通用類C#
- C#類中方法的執行順序C#
- 在.Net框架中 C# 實現多執行緒的同步方法詳解框架C#執行緒
- C#多執行緒(4):程式同步Mutex類C#執行緒Mutex
- 利用源生成器,在編譯階段生成對映程式碼,減少執行時反射編譯反射
- 如何知道一個Webapp執行時類的執行次序?WebAPP
- [深入學習C#]C#實現多執行緒的方法:執行緒(Thread類)和執行緒池(ThreadPool)C#執行緒thread
- .netCore System.Drawing.Common 釋出,在CentOS 執行報錯,生成圖片流時。會因為不支援在liunx平臺生成圖片。NetCoreCentOS
- .NET程式碼樹執行時間計時器
- 自適應查詢執行:在執行時提升Spark SQL執行效能SparkSQL
- 在APACHE上執行ASP.NET程式ApacheASP.NET
- 執行時生成其它EXE檔案(VB6)
- 在TQ2440上執行perf,生成Flame Graph
- Jmeter Arrivals thread group 在命令列執行時,無法生成測試結果JMeterthread命令列
- Android程式設計師必會技能—執行時動態生成類—之動態代理Android程式設計師
- Android程式設計師必會技能---執行時動態生成類---之動態代理Android程式設計師
- 在opendaylight專案開發時,執行maven命令生成專案骨架時,出現錯誤。Maven
- C#集合類(HashTable, Dictionary, ArrayList)與HashTable執行緒安全C#執行緒
- c#執行緒-執行緒同步C#執行緒
- linux 安裝dotnet 8.0執行時Linux
- 怎樣在 Kubernetes 上執行 PostgreSQLSQL
- 在kubernetes上執行WASM負載ASM負載
- 在 WASI 上執行 .NET 7 應用程式
- C#執行緒C#執行緒
- asp.net (C#)生成html檔案ASP.NETC#HTML
- 後臺執行以及保持程式在後臺長時間執行
- iOS 執行時獲取類的所有屬性iOS
- vc++生成程式不需要.net執行環境的可以執行exe程式的方法C++
- 利用Salamander .Net Linker生成可脫離.net framework環境執行的程式Framework
- 【乾貨分享】C# 實體類生成工具C#
- 理解c#的多執行緒的時間片分配C#執行緒
- C# 中利用執行時編譯實現泛函C#編譯
- 在JVM執行時開啟GC日誌JVMGC
- 在執行時刻更新功能模組 (轉)
- 生成 Linux 執行時間報告的 Bash 指令碼Linux指令碼