public static class CustomActivator
{
public static T CreateInstance<T>() where T : new()
{
return ActivatorImpl<T>.Factory();
}
private class ActivatorImpl<T> where T : new()
{
// Под колпаком используем Reflection.Emit!
private static readonly
System.Linq.Expressions.Expression<Func<T>> _expr = () => new T();
public static readonly Func<T> Factory = _expr.Compile();
}
}
正如上一个答案中已经提到的,有几种生成代码的方法。其中一些允许您在设计时生成代码,即 在 IDE (T4) 中。另一种是在运行时生成 C# 代码(CodeDom),第三种是在运行时生成可执行代码(即 IL)(Reflection.Emit)。
尽管后一种情况似乎很少见,但事实并非如此。想都没想,你就一直在使用这项业务。
编译正则表达式
是的,如果你像这样创建一个正则表达式
var regex = new Regex("abc", RegexOptions.Compiled);,那么运行时将使用 Reflection.Emit 生成一个带有自定义状态机的类,而不是每次都解释正则表达式。序列化器
是的,大多数序列化程序都使用反射,但它非常慢。因此,同样的XmlSerializer会在运行时生成一个特殊的序列化器类,并存放在一个特殊的文件夹中,以备后用。二进制序列化程序使用相同的方法,而人人喜爱的 Newtonsoft.Json 并不轻视代码生成。
PostSharp 和其他 AOP
例如,我们想为日志记录创建一个运行时装饰器。如果我们有一个标有 attribute 的虚方法
[LogMethod],那么我们可以生成一个继承类,它将覆盖这个虚方法并记录该方法的所有参数,然后才调用基实现。Postsharp 和其他 AOP 框架直接建立在运行时代码生成技术之上。
表达式树
是的,C# 中的表达式树是一种高级构造,但该方法
Expression.Compile实际上使用 Reflection.Emit 在运行时生成代码。对象到对象映射器
所有 Automappers 使用 Reflection.Emit 生成一种类型实例到另一种类型实例之间的映射。
对象关系管理
这是映射器的一个特例,它们充分利用了代码生成。NHibernate、LINQ 2 SQL、Entity Fraework 都在运行时生成类来将对象相互映射或拦截存储库方法调用。
模拟框架
Rhyno Mocks、Moq 等都基于表达式树或直接基于 Reflection.Emit(是的,许多模拟框架使用 DynamicProxy,它已经使用 Reflection.Emit)在测试时生成存根。
WCF 和其他通讯工具
是的,它们还为调用检查和其他运行时检测生成各种代理类。
Application Insights 和其他记录器/分析器
同样,为了使日志记录过程尽可能简单,此类工具中使用了代码生成。
DLR - 动态语言运行时
CLR 具有与动态类型语言(如 Python、Ruby 等)交互的特殊功能。在那里,DLR 被充分利用以提高性能。
信不信由你,您甚至可以使用 Reflection.Emit(尽管是间接的)来解决标准 C# 问题。
您可能知道
new T()仿制药的局限性在于恐惧和恐怖。它使用反射,严重影响性能,甚至将实例化过程中发生的异常包装在TargetInvocationException. 我已经在几个项目中遇到过这种方法会显着破坏端到端的执行时间。因此,在这些项目中,使用了一个专门的项目CustomActivactor:现在我们的激活器不会遇到性能问题,也不会在 nerdy 中包装构造函数异常
TargetInvocationExpression。Z.Y. 事实上,我只触及了部分脚本,同样的 ASP.NET 充分利用了代码生成。想要了解图片的丰满度,可以自己尝试搜索一下。例如,您可以从 System.Reflection.Emit 命名空间中查找ILGenerator类型或其他类型:
ILGenerator(没有语义搜索,那么多链接不会有效,但会清楚悲剧的规模)。更新:这个主题已经变成了一篇关于激活器、泛型和代码生成的复杂性的相当长的帖子——剖析 C# 中的 new() 约束:泄漏抽象的完美示例。
Reflection.Emit- 这适用于那些想要在纯 IL(简称硬核)上创建动态程序集(而不是向现有程序集添加内容)的人。更多细节如下。或者,也许您需要一些不同的东西,最好是更简单的东西?
如果你只想要一个动态类型,也许就好了
dynamic或者ExpandoObject?而且没有代码生成,您可以忘记在编译阶段进行检查。您是否需要一种方法(也可能是其中的一组)来接受和解析 lambda 表达式?然后使用表达式树。对于简单的情况,它会很好,但是如果您决定编写自己的 ORM,那么这个任务一点也不容易。但是请记住,表达式树仅支持单行 lambda 表达式。
如果您需要动态生成 HTML(突然),并且这不是 Web 应用程序,则使用RazorEngine。您还可以通过模拟 Web 应用程序来添加 Intellisense 支持。
代码生成
简而言之:如果您仍然需要完整的代码生成,请使用 Roslyn。因为它功能强大且平台无关。T4 也可能有用(尤其是当您需要非 C# 和非 VB 代码时)。
的优点和缺点
Reflection.Emit优点
Microsoft.CodeDom,但 Roslyn 一切都很好,因为它将编译器作为服务拉取)。Microsoft.CodeDom,它只特定于 Microsoft 实现,但 Roslyn 也是平台无关的)瑕疵
Reflection.Emit.基于此,决定是否使用它
Reflection.Emit。但我想这很硬核。备择方案
Microsoft.CodeDomMicrosoft.CodeDom相对Reflection.Emit编程方法
生成的带有帮助的代码
Microsoft.CodeDom可以是解决方案的一部分(LINQ to SQL、WSDL、XSD 类是通过这种方式生成的)。您还可以使用partial类来自定义生成的代码。使用时,Reflection.Emit您需要创建一个新程序集。当您使用时,
Reflecion.Emit您是在使用 IL 命令进行编程。当您使用Microsoft.CodeDomC# 编程时,您创建了 C# 命令。因此,代码
Microsoft.CodeDom将更容易创建,因为您正在使用更高级别的命令进行编程。在 IL 中创建的代码Reflecion.Emit将更难在 IL 中创建(例如,需要使用转换创建循环),尤其是在编写复杂逻辑时。由此可以推断出
Reflection.Emit更多的可能性(所以IL比C#有更多的可能性)。但是,对于 C# 中的每一个熟悉的东西,您将不得不编写更多的代码。表现
代码
Microsoft.CodeDom会比 慢Reflection.Emit,因为先编译,然后生成C#代码,再编译这个生成的代码。Reflection.Emit立即编译。平台(独立)依赖
正如@Grundy 正确指出的那样,这对于
Reflecion.Emit. 而且Microsoft.CodeDom它只能在 Microsoft 的平台上使用。罗斯林(基于问题)
但是,我不会使用
Microsoft.CodeDom. 因为 Roslyn 更强大,表现力更强。Microsoft.CodeDomcsc.exe 不同,它不仅仅是对 csc.exe 的包装,还可以对代码进行语义分析,动态编译和解析代码。这是为 C# 和 VB 创建任何工具的方法。因此,有可能从格式化代码到在解决方案中查找给定符号的所有引用。T4
但是,如果您不需要动态生成的程序集,而是需要补充现有解决方案的一部分怎么办?
或者如果您需要动态生成的文件,而不是 C# 或 VB?例如,假设是 JavaScript 或 CSS。(对于 HTML,如果有的话,我推荐 RazorEngine,但使用 T4 也可以)。或者甚至是一些异国情调的扩展。然而,使任务变得更糟的是,需要其中的几个文件,但数量只有在代码执行时才知道。
然后就可以使用T4了。它是 Visual Studio 中的一个工具(或只是一个控制台实用程序),用于生成任何语言的代码。在 MonoDevelop 中也可用。
我必须马上说 Entity Framework(不是 .NET Core)使用 T4。
概念上类似于 ASPX,只是 C# 插入之间的代码
<##>不一定是 HTML/CSS/JavaScript,而是目标文件的编程语言。手册。的缺点
现有的答案有些过时。除了它们之外,需要注意的是,在 .NET Core 和 .NET 5+ 中,System.Reflection.Emit (SRE) 的功能与 .NET Framework / Mono 相比是有限的:您可以在内存中生成动态程序集,但是您不能将它们保存到可执行文件中。这意味着如果有人想在他们的编译器中使用 SRE 作为代码生成的基础,这个想法将不得不在今天的 .NET 环境中被放弃。这不会干扰使用 SRE 生成序列化器/映射器,除了现在动态程序集不能在不同的程序启动之间缓存——它必须总是重新生成,即使是相同的类型。如果需要将动态程序集保存到文件中,您也可以使用Mono.Cecil或系统.反射.元数据。
此外,C# 最近还引入了动态代码生成的新功能,Source generators。在一些平时使用 SRE/CodeDom/T4 的情况下,现在可以尝试一下了。