用C#生成代码的最佳方法与使用装饰器类似?
假设我有一个不断重复的模式。比如:用C#生成代码的最佳方法与使用装饰器类似?,c#,C#,假设我有一个不断重复的模式。比如: 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
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);
}
}
<#@ 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);
}
<# } #>
}
唯一不同的是本机函数和包装器方法的名称。有没有一种方法可以通过类似于装饰器的东西来生成它们?起初,我认为C#attributes是decorators。也就是说,我可以通过类似[generatescapfolding(“MyNativeCall1”)]
的方式生成代码。但属性似乎更像注释,实例化了一个包含一些元数据的类
C#也没有宏。那么有没有办法做到这一点
需要记住的几件事:
- 其思想是包装器方法有额外的代码;它们不仅仅是调用本机函数
- 其思想还在于生成的代码可以与类内的其他现有代码交错,而不是生成类文件本身;类似于装饰器或C/C++宏的东西
- 这种方法不应该依赖于任何特定的IDE。具体来说,我不在VisualStudio上
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);
}
}
<#@ 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);
}
<# } #>
}
静态C类{
Visual Studio中的代码段仅用于此目的
看看这篇MSDN文章,它教您如何创建自己的自定义代码段。
编辑:
好的。我看到你编辑了你的问题,并补充说你不是在寻找特定于IDE的功能。因此我的回答现在变得无关紧要。不过,对于那些来搜索此问题并正在寻找内置Visual Studio功能的人来说,这可能是有用的。我想我会回答我自己的问题,但遗憾的是:不,没有办法这在C#中是不存在的。也就是说,语言本身和框架中都没有任何东西
但是,如果有一个在Visual Studio上,就像RGraham和Pradeep指出的那样,有一些模板;其他IDE也可能有不同的功能/功能来完成这一点。但是,同样,没有什么比C#本身的预处理器或装饰器更重要。您不需要IDE在运行时生成和处理模板,但您必须和/或
在模板中,您可以将模板语言与C代码混合使用(示例HTML生成):
这样,您就不必指定模板文件名,只需使用内存处理:
var host = new CustomCmdLineHost();
Engine engine = new Engine();
string input = File.ReadAllText("ExtDll.tt");
string source = engine.ProcessTemplate(input, host);
享受并请标记您的首选答案。这里是另一个选项,带有一点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中的原始存根永远不会被调用。您必须遵守一些命名约定
如果您将其与相结合,您将实现需要IMHO的脚手架。您可以使用?为什么不使用DllImport
的EntryPoint
属性来为所调用的方法指定不同的名称?[DllImport(“mydll”,EntryPoint=“MyNativeCall1”)]公共静态外部uint MyWrapper1(操作a);
@ShlomiBorovitz,因为我可能需要做的不仅仅是在包装器方法中调用本机函数;我忘了更清楚地说明这一点。如果您事先有一个方法列表,您是否自己研究过反射或发出IL?这是一个非常深刻和令人讨厌的东西,但可能会很好地工作。如果有必要,我会接受这个没有更好的方法。但是这些模板看起来有点混乱。我稍后会读更多关于它们的内容,但是你知道a:它们是否可以与类中的其余代码交错?比如,不是生成文件本身,而是在我正在处理的文件中插入文本(更像是C/C++宏)B:这只是VS功能吗?因为我没有使用VS。我应该在我的问题中添加这些详细信息。@SaldaVonSchwartz您可以在T4中编写基本定义,然后使用包含文件语法来包含任何现有代码。是的,这是Visual Studio功能,它在将代码发送到编译器之前生成代码-因此它对您的用户不起作用听起来你只是想要一个预构建的shell/batch/other脚本,但这不是C#的一部分,所以它总是依赖于你使用的任何IDE。实际上,除了装饰器,我想要的至少是像C预处理器这样的东西,任何编译器都能识别:/@SaldaVonSchwartz对不起,在C#languag中没有这样的东西e spec.C#没有预处理器。您将不得不依赖实际编译器之外的工具。可以在Visual Studio之外使用.tt系统,但通常需要一些手动操作。不仅是Visual Studio,还包括任何支持编译前/编译后命令的IDE(因此能够执行bash/shell).任何值得支持的IDE都应该支持这些。
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
};
}
}
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
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);
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
}