【问题标题】:C#: writing MSIL to add a preprocessor directiveC#:编写 MSIL 以添加预处理器指令
【发布时间】:2011-01-11 06:25:29
【问题描述】:

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

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

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

【问题讨论】:

  • 我想我明白你在说什么,你想要一个自定义属性应用到一个方法,如果满足一个条件就发出一个#warning指令。我认为你不能这样做,因为#warning 是一个编译时指令..
  • 顺便问下好问题...+1 来自我...

标签: c# reflection c-preprocessor compiler-warnings cil


【解决方案1】:

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

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

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

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

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

【讨论】:

  • 优秀的答案,以及我最终要遵循的路线。我不知道我是否应该选择你作为这个问题的选定答案,因为它没有直接回答我的 MSIL 问题,但你正在解决我最近的几个问题背后的问题!
  • 你是说他应该改用正则表达式吗? ;)
  • @Remus - 听起来你知道这句话。莎拉可能是她/他。不,这里没有指明正则表达式。
【解决方案2】:

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

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

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

您还有其他一些选择:

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

【讨论】:

    【解决方案3】:

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

    custom FxCop rule 可能会更好。

    【讨论】:

      【解决方案4】:

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

      class Person:
        [getter(FirstName)]
        _fname as string
      
        [getter(LastName)]
        _lname as string
      
        def constructor([required] fname, [required] lname):
          _fname = fname
          _lname = lname
      

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

      I've always wanted 这种可扩展性成为 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 中实现 code contracts 后编译器。

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

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

      【讨论】:

        【解决方案5】:

        您可以使用 post-build task 来执行此操作,该post-build task 反映您的编译代码并检查条件。我没有任何创建 MSBuild 任务的经验,但你可以从这里开始。

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

        【讨论】:

          【解决方案6】:

          我的直觉是不,您不能基于自定义属性和条件注入 #warning 指令,因为编译器会在编译时捕获它,这就像鸡和蛋的情况一样,因为自定义属性必须是在注入 #warning 之前首先进行评估,但为了发生这种情况,必须首先执行编译时操作。

          希望这会有所帮助, 最好的祝福, 汤姆。

          【讨论】:

            【解决方案7】:

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

            基本上是a指令(表示像'#warning'是CSC在指定条件下以某种方式表现的指令。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2021-01-31
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-09-23
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多