在运行时生成C# .NET类
本文译自:Generating C# .NET Classes at Runtime
作者:WedPort
在我的C#职业生涯中,有几次我不得不在运行时生成新的类型。希望把它写下来能帮助有相同应用需求的人。这也意味着我以后不必在查找相同问题的StackOverflow文章了。我最初是在.NET 4.6.2中这样做的,但我已经更新到为.NET Core 3.0提供了示例。所有代码都可以在我的GitHub上面找到。
GitHub:https://github.com/cheungt6/public/tree/master/ReflectionEmitClassGeneration
为什么我需要在运行时生成类?
在运行时生产新类型的需求通常是由于运行时才知道类属性,满足性能要求以及需要在新类型中添加功能。当你尝试这样做的时候,你应该考虑的第一件事是:这是否真的是一个明智的解决方案。在深入思考之前,还有很多其他事情可以尝试,问你自己这样的问题:
- 我可以使用普通的类吗
- 我可以使用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 properties
var _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来帮助您调试和理解运行时新类型的创建,以及如何更新值。如果您有任何问题或意见,请随时与我们联系。

在运行时生成C# .NET类的更多相关文章
- Javassist之使用字节码在运行时生成新的类 01
介绍 Javassist是一个开源的分析.编辑和创建Java字节码的类库.是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的.它已加入了开放源代码JBoss 应用 ...
- 【转】Sqlite 混合模式程序集是针对“v2.0.50727”版的运行时生成的,在没有配置其他信息的情况下,无法在 4.0 运行时中加载该...
开发环境: vs2010+.net framework 4.0+ System.Data.SQLite.DLL (2.0)今天在做Sqlite数据库测试,一运行程序在一处方法调用时报出了一个异常 混合 ...
- T-SQL 运行时生成语句
运行时生成语句 1.用EXECUTE执行动态命令 EXECUTE命令可以执行存储过程.函数和动态的字符串命令.注意此语句的作用正如前面在介绍批处理时,如果批中的第一条语句是"EXECUTE存 ...
- SQLite.dll混合模式程序集是针对“v2.0.50727”版的运行时生成的,在没有配置其他信息的情况下,无法在 4.0 运行时中加载该程序集。
其他信息: V5.7.4.4 Can't find the System.Data.SQLite.dll more info : 混合模式程序集是针对"v2.0.50727"版的运 ...
- C#异常--System.IO.FileLoadException:“混合模式程序集是针对“v2.0.50727”版的运行时生成的错误
异常信息: System.IO.FileLoadException:“混合模式程序集是针对“v2.0.50727”版的运行时生成的,在没有配置其他信息的情况下,无法在 4.0 运行时中加载该程序集.” ...
- C#程序集问题:混合模式程序集是针对“v2.0.50727”版的运行时生成的.....
今天在把以前写的代码生成工具从原来的.NET3.5升级到.NET4.0,同时准备进一步完善,将程序集都更新后,一运行程序在一处方法调用时报出了一个异常: 混合模式程序集是针对“v2.0.50727”版 ...
- Ubuntu16.04下写的Qt程序,调试时没问题,运行时偶现崩溃 (需要在运行时生成core dump文件,QMAKE_CC += -g)
记录一下 Ubuntu16.04下写的Qt程序,调试时没问题,运行时偶现崩溃 需要在运行时生成core dump文件 首先在pro结尾里加入 QMAKE_CC += -g QMAKE_CXX += - ...
- <VS2010>混合模式程序集是针对“v2.0”版的运行时生成的,在没有配置其他信息的情况下,无法在 4.0 运行时中加载该程序集
在把以前写的代码生成工具从原来的.NET3.5升级到.NET4.0时,将程序集都更新后,一运行程序在一处方法调用时报出了一个异常: 混合模式程序集是针对“v2.0.50727”版的运行时生成的,在没有 ...
- 混合模式程序集是针对“v2.0.50727”版的运行时生成的
混合模式程序集是针对“v2.0.50727”版的运行时生成的,在没有配置其他信息的情况下,无法在 4.0 运行时中加载该程序集. 由于“system.data.sqlite.dll”不完整造成的. 在 ...
随机推荐
- Life In Changsha College- 第三次冲刺
第三次冲刺任务 设计登录注册功能. 用户故事 用户打开“生活在长大”的界面,选择登录 已注册过则输入用户名和密码直接登录 未注册用户则可选择注册功能,注册成功后登录 登录成功则弹出提示框 系统结构图环 ...
- Cypress系列(4)- 解析 Cypress 的默认文件结构
如果想从头学起Cypress,可以看下面的系列文章哦 https://www.cnblogs.com/poloyy/category/1768839.html 默认文件结构 在使用 cypress o ...
- MethodHandle(方法句柄)系列之一:MethodHandle和MethodType
阅读此文章的作者建议先了解java反射和动态代理. java7中为间接调用方法引入了新的api,其中最关键的是java.lang.invoke包,即方法句柄.我们可以看成是java ...
- 使用turtle库画国际象棋棋盘
import turtle n = 60 # 每行间隔,小格子边长 x = -300 # x初始值 y = -300 # x初始值 def main(): turtle.speed(11) turtl ...
- (Java实现) 子集和问题
回溯算法也叫试探法,它是一种系统地搜索问题的解的方法.回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试.用回溯算法解决问题的一般步骤为: 1.定义一个解空间,它包含问题的解 ...
- Java实现 蓝桥杯 算法训练 猴子吃包子(暴力)
试题 算法训练 猴子吃包子 问题描述 从前,有一只吃包子很厉害的猴子,它可以吃无数个包子,但是,它吃不同的包子速度也不同:肉包每秒钟吃x个:韭菜包每秒钟吃y个:没有馅的包子每秒钟吃z个:现在有x1个肉 ...
- Java实现 蓝桥杯 算法训练 区间k大数
算法训练 区间k大数查询 时间限制:1.0s 内存限制:256.0MB 问题描述 给定一个序列,每次询问序列中第l个数到第r个数中第K大的数是哪个. 输入格式 第一行包含一个数n,表示序列长度. 第二 ...
- Java实现蓝桥杯VIP 算法训练 P0502
试题 算法训练 P0502 资源限制 时间限制:1.0s 内存限制:256.0MB 编写一个程序,读入一组整数,这组整数是按照从小到大的顺序排列的,它们的个数N也是由用户输入的,最多不会超过20.然后 ...
- Linux 文件特殊权限-SetUID
SetUID非常类似于Windows中以管理员身份来运行文件,针对的是可执行文件,而且命令执行者要拥有对这个文件的执行权限,只在文件执行的过程中变换身份,最常见的passwd命令就具有SetUID权限 ...
- Centos7 搭建KVM并创建Linux Windows虚拟机
一.安装KVM 查看系统版本 cat /etc/redhat-release 关闭防火墙及selinux systemctl disable firewalld.service 查看防 ...