在c#中生成代码的最佳方式,类似于使用装饰器
本文关键字:类似于 方式 最佳 代码 | 更新日期: 2023-09-27 18:10:07
假设我有一个不断重复的模式。比如:
static class C {
[DllImport("mydll")]
private static extern uint MyNativeCall1(Action a);
public static uint MyWrapper1(Action a) {
// Do something
return MyNativeCall1(a);
}
[DllImport("mydll")]
private static extern uint MyNativeCall2(Action a);
public static uint MyWrapper2(Action a) {
// Do something
return MyNativeCall2(a);
}
//...
[DllImport("mydll")]
private static extern uint MyNativeCallN(Action a);
public static uint MyWrapperN(Action a) {
// Do something
return MyNativeCallN(a);
}
}
唯一不同的是本机函数和包装方法的名称。有没有一种方法可以通过装饰器之类的东西来生成它们?一开始我以为c#属性是装饰器。也就是说,我可以通过[GenerateScaffolding("MyNativeCall1")]
生成代码。但属性似乎更像是注释,实例化了一个包含一些元数据的类。
c#也没有宏。有什么办法可以做到吗?
要记住的几件事:
- 这个想法是包装器方法有额外的代码;它们不只是调用本地函数。
- 这个想法也是,生成的代码可以与其他现有的代码交织在一个类中,而不是生成类文件本身;比如装饰器或者C/c++宏。
- 该方法不应该依赖于任何特定的IDE。具体来说,我没有使用Visual Studio。
从这篇关于T4模板的MSDN文章中获得灵感,您可以这样做:
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".cs" #>
static class C {
<#
int N = 15;
for(int i=0; i<N; i++)
{ #>
[DllImport("mydll")]
private static extern uint MyNativeCall<#= i #>(Action a);
public static uint MyWrapper<%#= i #>(Action a) {
return MyNativeCall<#= i #>(a);
}
<# } #>
}
您不需要IDE在运行时生成和处理模板,但您必须创建自己的指令处理器和/或主机。
Engine engine = new Engine();
//read the text template
string input = File.ReadAllText(templateFileName);
//transform the text template
string output = engine.ProcessTemplate(input, host);
在你的模板中,你可以混合使用模板语言和c#代码(生成HTML示例):
<table>
<# for (int i = 1; i <= 10; i++)
{ #>
<tr><td>Test name <#= i #> </td>
<td>Test value <#= i * i #> </td>
</tr>
<# } #>
</table>
下面是我如何使用T4 从文本文件生成各种状态机您甚至可以在运行时生成 c#类的源代码,编译和加载并从程序中执行。
如果您将所有这些技术结合起来,甚至可能使用可组合的部分,如MEF,我相信您将能够实现您所需要的。
UPDATE不带MEF,但您仍然需要IDE来预处理模板
因为我没有你的DLL,我不能给你一个确切的答案,但也许这会有所帮助。
给定这个模板(ExtDll.tt):
<#@ template language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="mscorlib" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#
var extraCodeArray = new[]
{ string.Empty,
"var localVar = 1;",
"var localVar = 2;",
"var localVar = 3;",
"var localVar = 4;",
"var localVar = 5;",
"var localVar = 6;",
"var localVar = 7;",
"var localVar = 8;",
"var localVar = 9;",
"var localVar = 10;",
};
#>
using System;
static class C{
<# for (int i = 1; i <= 10; i++)
{ #>
public static double MyWrapper<#= i #>(Func<int,double> a) {
<#= extraCodeArray[i] #>
return a.Invoke(localVar);
}
<# } #>
}
和这个程序:
using System;
using System.Linq;
namespace ConsoleApplication4
{
using System.CodeDom.Compiler;
using System.Reflection;
using Microsoft.CSharp;
class Program
{
static void Main(string[] args)
{
ExtDll code = new ExtDll();
string source = code.TransformText();
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters()
{
GenerateInMemory = true,
GenerateExecutable = false
};
parameters.ReferencedAssemblies.AddRange(
new[]
{
"System.Core.dll",
"mscorlib.dll"
});
CompilerResults results = provider.CompileAssemblyFromSource(parameters, source);
if (results.Errors.HasErrors)
{
var errorString = String.Join("'n", results.Errors.Cast<CompilerError>().Select(error => String.Format("Error ({0}): {1}", error.ErrorNumber, error.ErrorText)));
throw new InvalidOperationException(errorString);
}
Assembly assembly = results.CompiledAssembly;
Func<int,double> squareRoot = (i) => { return Math.Sqrt(i); };
Type type = assembly.GetType("C");
//object instance = Activator.CreateInstance(type);
MethodInfo method = type.GetMethod("MyWrapper4");
Console.WriteLine(method.Invoke(null, new object[]{squareRoot}));
}
}
}
它将输出2,因为它是4的平方根。
更新2
在稍微修改了上面第二个链接中的CustomCmdLineHost之后:
public IList<string> StandardAssemblyReferences
{
get
{
return new string[]
{
//If this host searches standard paths and the GAC,
//we can specify the assembly name like this.
//---------------------------------------------------------
//"System"
//Because this host only resolves assemblies from the
//fully qualified path and name of the assembly,
//this is a quick way to get the code to give us the
//fully qualified path and name of the System assembly.
//---------------------------------------------------------
typeof(System.Uri).Assembly.Location,
typeof(System.Linq.Enumerable).Assembly.Location
};
}
}
示例程序不再需要IDE:
var host = new CustomCmdLineHost();
host.TemplateFileValue = "ExtDll.tt";
Engine engine = new Engine();
string input = File.ReadAllText("ExtDll.tt");
string source = engine.ProcessTemplate(input, host);
if (host.Errors.HasErrors)
{
var errorString = String.Join("'n", host.Errors.Cast<CompilerError>().Select(error => String.Format("Error ({0}): {1}", error.ErrorNumber, error.ErrorText)));
throw new InvalidOperationException(errorString);
}
CSharpCodeProvider provider = new CSharpCodeProvider();
... rest of the code as before
我希望这能满足你的需要。
更新3
如果您像这样进一步修改示例主机:
internal string TemplateFileValue = Path.Combine(
AppDomain.CurrentDomain.BaseDirectory,"CustomCmdLineHost.tt");
那么你可以避免指定模板文件名,只使用内存处理:
var host = new CustomCmdLineHost();
Engine engine = new Engine();
string input = File.ReadAllText("ExtDll.tt");
string source = engine.ProcessTemplate(input, host);
Visual Studio中的代码片段仅用于此目的。
看看这篇MSDN文章,它教你如何创建自己的自定义代码片段。http://msdn.microsoft.com/en-us/library/ms165394.aspx
编辑:
好吧. .我看到你编辑了你的问题,并补充说你不是在寻找IDE特定的功能。所以我的回答现在变得无关紧要了。尽管如此,对于正在搜索此问题并正在寻找Visual Studio内置功能的人来说,它可能很有用。
我想我会用一个悲伤的回答来回答我自己的问题:不。在c#中没有办法做到这一点。也就是说,语言本身和框架中没有任何内容。
然而,如果是在Visual Studio上,有模板,就像RGraham和Pradeep指出的那样;其他ide可能也有不同的工具/特性来完成此任务。但是c#本身没有预处理器或装饰器。
这是另一个选项,带有一点PostSharp的AOP的魔力:
using System;
using System.Reflection;
using PostSharp.Aspects;
internal class Program
{
#region Methods
private static void Main(string[] args)
{
Action action = () => { Console.WriteLine("Action called."); };
Console.WriteLine(C.MyWrapper1(action));
}
#endregion
}
[Scaffolding(AttributeTargetMembers = "MyWrapper*")]
internal static class C
{
#region Public Methods and Operators
public static uint MyWrapper1(Action a)
{
DoSomething1();
return Stub(a);
}
#endregion
#region Methods
private static void DoSomething1() { Console.WriteLine("DoSomething1"); }
private static uint Stub(Action a) { return 0; }
#endregion
}
internal static class ExternalStubClass
{
#region Public Methods and Operators
public static uint Stub(Action a)
{
a.Invoke();
return 5;
}
#endregion
}
[Serializable]
public class ScaffoldingAttribute : OnMethodBoundaryAspect
{
#region Fields
private MethodInfo doSomethingInfo;
#endregion
#region Public Methods and Operators
public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo)
{
Type type = typeof(C);
this.doSomethingInfo = type.GetMethod(method.Name.Replace("MyWrapper", "DoSomething"), BindingFlags.NonPublic | BindingFlags.Static);
}
public override void OnEntry(MethodExecutionArgs args)
{
this.doSomethingInfo.Invoke(null, null);
args.ReturnValue = ExternalStubClass.Stub(args.Arguments[0] as Action);
args.FlowBehavior = FlowBehavior.Return;
}
#endregion
}
这个示例基本上展示了一个类中的方法如何被另一个具有相同名称的类中的方法动态覆盖。C中的原始存根将永远不会被调用。你必须遵守一些命名约定。
如果您将它与动态绑定的本地DLL方法结合使用,您将获得所需的脚手架