简体   繁体   English

C#:编写MSIL以添加预处理程序指令

[英]C#: writing MSIL to add a preprocessor directive

Is it possible in C# to write MSIL code that will add a preprocessor directive to the code, eg, #warning , if a certain condition is met? 是否有可能在C#中编写MSIL代码,将代码添加预处理程序指令,例如#warning ,如果满足某个条件? Or maybe this can be done with reflection, I don't know. 或许这可以通过反思完成,我不知道。

I'm trying to write a custom attribute that, if applied incorrectly to a class's method or property, will generate a compiler warning. 我正在尝试编写一个自定义属性,如果错误地应用于类的方法或属性,将生成编译器警告。 Using the existing Obsolete attribute won't work because then just using my custom attribute causes the warning, and I don't want that. 使用现有的Obsolete属性将不起作用,因为只使用我的自定义属性会导致警告,我不希望这样。 I want the custom attribute constructor to check for a condition, and if that condition is true then cause a compilation warning. 我希望自定义属性构造函数检查条件,如果该条件为true,则导致编译警告。

Update: after reading back over my question, I think what I'm asking for is impossible just because I'm mixing compile-time and runtime constraints. 更新:在回顾了我的问题之后,我认为我所要求的是不可能的,因为我正在混合编译时和运行时约束。 I think I'll end up going with a post-build task to check the just-built DLL and have it spit out error messages if the condition is true. 我想我最终将使用一个后期构建任务来检查刚刚构建的DLL,如果条件为真,它会发出错误消息。

I saw this question coming from your previous thread. 我看到这个问题来自你以前的帖子。 To mis-quote the great Jamie Zawinski: "Some people, when confronted with a problem, think "I know, I'll use an attribute." Now they have two problems". 错误引用伟大的Jamie Zawinski:“有些人在遇到问题时会想”我知道,我会使用一个属性。“现在他们有两个问题”。

An attribute is merely out-of-band data, compiled into an assembly's metadata. 属性仅仅是带外数据,编译为程序集的元数据。 It cannot affect program execution or tool behavior, unless the program or the tool is explicitly programmed to recognize the specific attribute. 除非程序或工具明确编程为识别特定属性,否则它不会影响程序执行或工具行为。 It needs to do so using Reflection. 它需要使用Reflection来实现。

What you need to do is write your own tool. 你需要做的是编写自己的工具。 It should execute after an assembly is built, using the Post-Build step for a project. 它应该在构建程序集后执行,使用项目的Post-Build步骤。 It needs to load the assembly and use Reflection to iterate the types in the assembly. 它需要加载程序集并使用Reflection来迭代程序集中的类型。 For each type, iterate the methods with Type.GetMethods() and use MethodInfo.GetCustomAttributes() to discover and construct an attribute that might have been programmed. 对于每种类型,使用Type.GetMethods()迭代方法,并使用MethodInfo.GetCustomAttributes()来发现和构造可能已编程的属性。

You can use Type.GetInterfaces() to discover which interfaces are implemented by the type. 您可以使用Type.GetInterfaces()来发现类型实现的接口。 You can now complain when you see that a method is present that implements an interface method but is missing an attribute that says so. 您现在可以在看到存在实现接口方法但缺少属性的方法时抱怨。 And your ultimate goal: you can complain when you see a method with an attribute that says it implements an interface method but the type no longer inherits it. 并且你的最终目标是:当你看到一个带有一个属性的方法时,你会抱怨它说它实现了一个接口方法,但是类型不再继承它。

Use Environment.ExitCode to make the tool fail the build if you see anything objectionable. 如果您发现任何令人反感的事情,请使用Environment.ExitCode使该工具失败。 This takes care of enforcement. 这需要执行。 Btw: programmers really hate to break the build. 顺便说一句:程序员真的很讨厌打破构建。 That might well encourage them to use the attribute religiously. 这可能会鼓励他们虔诚地使用这个属性。 Or it might encourage them to edit the post build step. 或者它可能会鼓励他们编辑后期构建步骤。

The compiler stores two things for custom attributes: 编译器为自定义属性存储两件事:

  • The attribute constructor to call 要调用的属性构造函数
  • The data for each parameter to pass to the constructor 要传递给构造函数的每个参数的数据

The constructor is only called when the application is running and someone calls GetCustomAttributes for your Assembyl, Type, MethodInfo, ParameterInfo, etc. 构造函数仅在应用程序运行时调用,并且有人为您的Assembyl,Type,MethodInfo,ParameterInfo等调用GetCustomAttributes。

You have some other options to consider: 您还有其他一些选择:

  • Write a custom MSBuild task that runs after the compilation stage, loads the compiled assembly and checks the application's attribute usage. 编写在编译阶段之后运行的自定义MSBuild任务,加载已编译的程序集并检查应用程序的属性用法。
  • Use the AttributeUsage attribute to specify the code items to which the attribute can be applied. 使用AttributeUsage属性指定可以应用该属性的代码项。
  • Defer the attribute validation to runtime. 将属性验证推迟到运行时。

In short, no. 简而言之,没有。 Pre-processing directives have no IL representation, since they only exist as metadata used during compilation of a source file. 预处理指令没有IL表示,因为它们仅作为在编译源文件期间使用的元数据存在。

The kind of thing you're doing might be better as a custom FxCop rule . 作为自定义FxCop规则 ,您正在做的事情可能会更好。

Have you ever heard of Boo ? 你听说过Boo吗? It has interesting ways in which you can hook up into the compiler pipeline. 它有一些有趣的方法可以连接到编译器管道。 One such feature is called syntactic attributes , which are attributes that implement an interface that the compiler calls, so that they can participate in code generation. 一个这样的特性称为语法属性 ,它是实现编译器调用的接口的属性,以便它们可以参与代码生成。

class Person:
  [getter(FirstName)]
  _fname as string

  [getter(LastName)]
  _lname as string

  def constructor([required] fname, [required] lname):
    _fname = fname
    _lname = lname

The attributes in this code will generate public getters for the fields and null checks for the constructor parameters. 此代码中的属性将为字段生成公共getter,并为构造函数参数进行null检查。 It will all end up in the compiled assembly. 它将最终都在编译的程序集中。

I've always wanted this kind of extensibility to be part of the C# compiler. 我一直希望这种可扩展性成为C#编译器的一部分。 Maybe it will one day. 也许有一天会。 Until then, you can use a post-compiler, like CciSharp . 在此之前,您可以使用后编译器,如CciSharp CCiSharp will rewrite the CIL based on special attributes in the assembly, just like with Boo synctatic attributes. CCiSharp将根据程序集中的特殊属性重写CIL ,就像Boo synctatic属性一样。

Given this code: 鉴于此代码:

class Foo {
  [Lazy]
  public int Value { 
    get { return Environment.Ticks; } 
  }
}

CCiSharp will mutate the code based on the LazyAttribute to this: CCiSharp会产生变异基础上的代码LazyAttribute这样:

class Foo {
  int Value$Value; // compiler generated
  int Value$Initialized;
  int GetValueUncached() { 
    return Environment.Ticks;
  }
  public int Value  {
    get {
      if(!this.Value$Initialized) {
        this.Value$Value = this.GetValueUncached();
        this.Value$Initialized = true;
      }
      return this.Value$Value;
    }
}

CCiSharp is based on the Common Compiler Infrastructure project, the same used to implement the code contracts post compiler in the upcoming .NET Framework 4.0. CCiSharp基于Common Compiler Infrastructure项目,同样用于在即将推出的.NET Framework 4.0中实现编译后的代码契约

So this is how you can mutate the generated CIL. 所以这就是你如何改变生成的CIL。

But, a #warning directive does not have a CIL representation, it's a compiler directive only. 但是,# #warning指令没有CIL表示,它只是一个编译器指令。 To add this directive, what you must mutate is not the generated CIL, but the C# code itself. 要添加此指令,必须改变的不是生成的CIL,而是C#代码本身。 You'd have to implement a C# parser for that. 你必须为它实现一个C#解析器。 I think the best option is, as stated in other responses, to create a post-build event that will reflect over the generated assembly and issue the desired warning. 我认为,如其他回复中所述,最佳选择是创建一个后期构建事件,该事件将反映生成的程序集并发出所需的警告。

You could probably do this with a post-build task that reflects your compiled code and checks for the condition. 您可以使用反映编译代码并检查条件的构建后任务来执行此操作。 I don't have any experience creating MSBuild tasks but it's a place you can start. 我没有任何创建MSBuild任务的经验,但它是一个你可以开始的地方。

Another suggestion once .NET 4.0 is released is to use Code Contracts to specify requirements for the parameters of the attribute's constructor. .NET 4.0发布后的另一个建议是使用代码约定来指定属性构造函数参数的要求。 This would be caught at compile time. 这将在编译时捕获。

My gut feeling is no you cannot inject a #warning directive based on a custom attribute and condition since that is caught at compile time by the compiler, it is like a chicken and the egg situation as the custom attribute would have to be evaluated first before injecting a #warning but in order for that to happen a compile time action must be carried out first. 我的直觉是,你不能根据自定义属性和条件注入一个#warning指令,因为编译器会在编译时捕获它,它就像鸡和鸡蛋一样,因为自定义属性必须先评估注入一个#warning但为了实现这一点,必须首先执行编译时操作。

Hope this helps, Best regards, Tom. 希望这会有所帮助,最好的问候,汤姆。

I suspect the answer would be no, you can't, because the #warning directive is a CSC thing (ie you are directing the compiler to act in a certain fashion). 我怀疑答案是否定的,你不能,因为#warning指令是一个CSC的东西(即你指示编译器以某种方式行事)。 When writing raw MISL, obviously, CSC doesn't come into mix, therefore there is no compiler to direct to do something. 在编写原始MISL时,显然,CSC没有混合,因此没有编译器可以指导做某事。

Basically the a directive (indicate like '#warning' is an instruction to CSC to behave in a certain way under specified conditions. 基本上a指令(表示'#warning'是CSC在特定条件下以某种方式表现的指令。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM