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

发布于 2024-08-19 08:52:01 字数 351 浏览 2 评论 0原文

是否可以在 C# 中编写 MSIL 代码,在满足特定条件时将预处理器指令添加到代码中,例如 #warning?或者也许这可以通过反思来完成,我不知道。

我正在尝试编写一个自定义属性,如果该属性错误地应用于类的方法或属性,则会生成编译器警告。使用现有的 Obsolete 属性将不起作用,因为仅使用我的自定义属性就会导致警告,而我不希望出现这种情况。我希望自定义属性构造函数检查条件,如果该条件为真,则会引发编译警告。

更新:读完我的问题后,我认为我所要求的是不可能的,因为我混合了编译时和运行时约束。我想我最终会执行构建后任务来检查刚刚构建的 DLL,并在条件成立时让它吐出错误消息。

Is it possible in C# to write MSIL code that will add a preprocessor directive to the code, e.g., #warning, if a certain condition is met? 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. I want the custom attribute constructor to check for a condition, and if that condition is true then cause a compilation warning.

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

烟沫凡尘 2024-08-26 08:52:02

我从你之前的帖子中看到这个问题。错误地引用了伟大的杰米·扎文斯基(Jamie Zawinski)的话:“有些人在遇到问题时会想:“我知道,我会使用一个属性。”现在他们有两个问题”。

属性只是带外数据,被编译到程序集的元数据中。它不会影响程序执行或工具行为,除非程序或工具被明确编程为识别特定属性。它需要使用反射来做到这一点。

您需要做的就是编写自己的工具。它应该在构建程序集后执行,使用项目的构建后步骤。它需要加载程序集并使用反射来迭代程序集中的类型。对于每种类型,使用 Type.GetMethods() 迭代方法并使用 MethodInfo.GetCustomAttributes() 来发现和构造可能已编程的属性。

您可以使用 Type.GetInterfaces() 来发现该类型实现了哪些接口。现在,当您看到存在实现接口方法的方法但缺少这样的属性时,您可以抱怨。你的最终目标是:当你看到一个方法的属性表明它实现了一个接口方法,但该类型不再继承它时,你可以抱怨。

如果您发现任何令人反感的内容,请使用Environment.ExitCode 使该工具构建失败。这负责执行。顺便说一句:程序员真的讨厌破坏构建。这很可能会鼓励他们虔诚地使用该属性。或者它可能会鼓励他们编辑构建后步骤。

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".

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.

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. It needs to load the assembly and use Reflection to iterate the types in the assembly. For each type, iterate the methods with Type.GetMethods() and use MethodInfo.GetCustomAttributes() to discover and construct an attribute that might have been programmed.

You can use Type.GetInterfaces() to discover which interfaces are implemented by the type. 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. 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.

梅倚清风 2024-08-26 08:52:02

编译器为自定义属性存储两件事:

  • 要调用的属性构造函数 要
  • 传递给构造函数的每个参数的数据

仅当应用程序运行并且有人为您的 Assembyl、Type、MethodInfo、ParameterInfo 等调用 GetCustomAttributes 时,才会调用构造函数。

您还需要考虑一些其他选项:

  • 编写一个自定义 MSBuild 任务,该任务在编译阶段之后运行,加载已编译的程序集并检查应用程序的属性使用情况。
  • 使用 AttributeUsage 属性指定可以应用该属性的代码项。
  • 将属性验证推迟到运行时。

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.

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.
  • Use the AttributeUsage attribute to specify the code items to which the attribute can be applied.
  • Defer the attribute validation to runtime.
长梦不多时 2024-08-26 08:52:02

简而言之,不。预处理指令没有 IL 表示,因为它们仅作为源文件编译期间使用的元数据存在。

作为自定义 FxCop 规则,您正在做的事情可能会更好。

In short, no. Pre-processing directives have no IL representation, since they only exist as metadata used during compilation of a source file.

The kind of thing you're doing might be better as a custom FxCop rule.

懒猫 2024-08-26 08:52:02

您听说过 Boo 吗?它有一些有趣的方式可以连接到编译器管道。其中一项功能称为语法属性,它是实现编译器调用的接口的属性,以便它们可以参与代码生成。

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

  [getter(LastName)]
  _lname as string

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

此代码中的属性将为字段生成公共 getter,并为构造函数参数生成 null 检查。一切都将最终出现在编译后的程序集中。

我一直想要这种可扩展性成为 C# 编译器的一部分。也许有一天会的。在此之前,您可以使用后编译器,例如 CciSharp。 CCiSharp 将根据程序集中的特殊属性重写 CIL,就像 Boo 同步属性一样。

给定以下代码:

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

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 基于 通用编译器基础设施项目,同样用于实现代码契约 即将推出的 .NET Framework 4.0 中的后期编译器。

这就是改变生成的 CIL 的方法。

但是,#warning 指令没有 CIL 表示,它只是编译器指令。要添加此指令,您必须改变的不是生成的 CIL,而是 C# 代码本身。您必须为此实现一个 C# 解析器。我认为最好的选择是,正如其他回复中所述,创建一个构建后事件,该事件将反映生成的程序集并发出所需的警告。

Have you ever heard of 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. It will all end up in the compiled assembly.

I've always wanted this kind of extensibility to be part of the C# compiler. Maybe it will one day. Until then, you can use a post-compiler, like CciSharp. CCiSharp will rewrite the CIL based on special attributes in the assembly, just like with Boo synctatic attributes.

Given this code:

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

CCiSharp will mutate the code based on the LazyAttribute to this:

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.

So this is how you can mutate the generated CIL.

But, a #warning directive does not have a CIL representation, it's a compiler directive only. To add this directive, what you must mutate is not the generated CIL, but the C# code itself. You'd have to implement a C# parser for that. 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.

时光礼记 2024-08-26 08:52:02

您可以使用反映您的构建后任务来完成此操作编译代码并检查条件。我没有任何创建 MSBuild 任务的经验,但您可以从这里开始。

.NET 4.0 发布后的另一个建议是使用 代码契约 指定属性构造函数的参数要求。这将在编译时被捕获。

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.

Another suggestion once .NET 4.0 is released is to use Code Contracts to specify requirements for the parameters of the attribute's constructor. This would be caught at compile time.

赏烟花じ飞满天 2024-08-26 08:52:02

我的直觉是不,你不能根据自定义属性和条件注入 #warning 指令,因为编译器在编译时捕获它,这就像先有鸡还是先有蛋的情况,因为必须先评估自定义属性注入#warning,但为了实现这一点,必须首先执行编译时操作。

希望这有帮助,
此致,
汤姆.

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.

Hope this helps,
Best regards,
Tom.

抚笙 2024-08-26 08:52:02

我怀疑答案是否定的,你不能,因为 #warning 指令是 CSC 的事情(即你指示编译器以某种方式行事)。显然,在编写原始 MISL 时,CSC 不会混合,因此没有编译器可以指导执行某些操作。

基本上,a 指令(如“#warning”所示)是 CSC 在指定条件下以某种方式行事的指令。

I suspect the answer would be no, you can't, because the #warning directive is a CSC thing (i.e. you are directing the compiler to act in a certain fashion). When writing raw MISL, obviously, CSC doesn't come into mix, therefore there is no compiler to direct to do something.

Basically the a directive (indicate like '#warning' is an instruction to CSC to behave in a certain way under specified conditions.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文