8

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

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

更新: 在阅读了我的问题之后,我认为我所要求的只是因为我混合了编译时和运行时约束。我想我最终会执行一个构建后的任务来检查刚刚构建的 DLL,如果条件为真,让它吐出错误消息。

4

7 回答 7

6

我看到这个问题来自你之前的帖子。错误地引用伟大的 Jamie Zawinski 的话:“有些人在遇到问题时会想‘我知道,我会使用一个属性。’现在他们有两个问题”。

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

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

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

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

于 2010-01-30T20:49:13.793 回答
2

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

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

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

您还有其他一些选择要考虑:

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

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

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

于 2010-01-30T19:49:18.093 回答
2

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

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 基于Common Compiler Infrastructure项目,用于在即将推出的 .NET Framework 4.0 中实现代码协定后编译器。

所以这就是你可以改变生成的 CIL 的方法。

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

于 2010-02-06T23:20:30.247 回答
1

您可能可以使用反映已编译代码并检查条件的构建后任务来执行此操作。我没有任何创建 MSBuild 任务的经验,但它是您可以开始的地方。

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

于 2010-01-30T19:43:51.020 回答
1

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

希望这会有所帮助,最好的问候,汤姆。

于 2010-01-30T20:31:13.383 回答
0

我怀疑答案是否定的,你不能,因为 #warning 指令是 CSC 的东西(即你正在指导编译器以某种方式运行)。显然,在编写原始 MISL 时,CSC 不会混合在一起,因此没有编译器可以指导做某事。

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

于 2010-01-30T19:45:44.040 回答