这是一个C# 4.0编译器可选参数的bug吗?

13

我正在编写自定义安全属性并遇到了奇怪的编译器行为...当我在同一文件中使用该属性时,缺省参数值可以正常工作:

using System.Security.Permissions;

[System.Serializable]
sealed class FooAttribute : CodeAccessSecurityAttribute {
    public FooAttribute(SecurityAction action = SecurityAction.Demand) : base(action) { }
    public override System.Security.IPermission CreatePermission() { return null; }
}

[Foo] class Program {
    static void Main(string[] args) { }
}

但是当我将上述代码分成两个文件,如下所示 - 文件1:

using System.Security.Permissions;

[System.Serializable]
sealed class FooAttribute : CodeAccessSecurityAttribute {
    public FooAttribute(SecurityAction action = SecurityAction.Demand) : base(action) { }
    public override System.Security.IPermission CreatePermission() { return null; }
}

还有第二个文件:

[Foo] class Program {
    static void Main(string[] args) { }
}

我遇到了一个编译器错误:

错误: 'FooAttribute' 不包含任何接受0个参数的构造函数

只有在使用继承自CodeAccessSecurityAttribute的属性时才会出现这种情况,看起来非常奇怪...


2
源代码中的FooAttribute和错误信息中的DebugCallTraceAttribute之间有什么关系? - Eric Lippert
2
第一个片段中使用SecurityAttribute,第二个片段中使用CodeAccessSecurityAttribute,这是有原因的吗?对于有效的最小测试用例来说,它们似乎应该是相同的。(例如,它是否与此更改相关或者与在两个文件中有关或者两者都有关?) - user166390
抱歉,莫斯科时间已经是凌晨三点了,所有代码片段中的错误都已经修复。 - controlflow
非常酷的错误。如果Eric没有出现,那么请在connect.microsoft.com上发布此内容。请发布链接,我想知道这是如何出错的。顺便说一下,你需要修复编译器错误片段,因为它与你的代码不匹配。 - Hans Passant
2
这似乎与以下内容相关:https://dev59.com/ZVTTa4cB1Zd3GeqPrVEJ - joncham
1个回答

14

我没有确切的答案,但我已经尽力调查了。在我看来,如果你继承自 CodeAccessSecurityAttribute 而不是 SecurityAttribute,就会出现这种情况。当应用 Foo 属性时生成的 IL 代码如下:

.permissionset demand = {class 'ConsoleApplication1.FooAttribute, ConsoleApplication1, Version=1.0.0.0, Culture=neutral' = {}}

Foo 继承自 SecurityAttribute 时,它看起来像这样:

.custom instance void ConsoleApplication1.FooAttribute::.ctor(valuetype [mscorlib]System.Security.Permissions.SecurityAction) = ( 01 00 02 00 00 00 00 00 ) 

显然,CodeAccessSecurityAttribute通过应用该属性极大地改变了生成的IL代码。

如果我们将Foo声明更改为以下形式,则可以更深入地查看IL:

[Foo(SecurityAction.Demand)]
我们得到了以下的IL代码:
.permissionset demand = {class 'ConsoleApplication1.FooAttribute, ConsoleApplication1, Version=1.0.0.0, Culture=neutral' = {}}

如果不指定可选参数,它与我们之前的代码相同。此外,我们可以通过将属性和Program类拆分为单独的文件,或者按以下方式重新排列类中的文件来引发错误:

[Foo]
class Program
{

    static void Main(string[] args) {}


}

[System.Serializable]
sealed class FooAttribute : CodeAccessSecurityAttribute
{
    public FooAttribute(SecurityAction action = SecurityAction.Demand) : base(action) { }
    public override System.Security.IPermission CreatePermission() { return null; }
}

如果我们使用类OtherOther2执行以下操作,会更加有趣,这样做会出错,但是Program不会出错。只有在文件中位于Foo之前的类才会出错。

 [Foo]
 class Other
 {

 }

 [Foo]
 class Other2
 {
 }

 [System.Serializable]
 sealed class FooAttribute : CodeAccessSecurityAttribute
 {
      public FooAttribute(SecurityAction action = SecurityAction.Demand) : base(action) { }
      public override System.Security.IPermission CreatePermission() { return null; }        }

 [Foo]
 class Program
 {

  static void Main(string[] args) {}
 }
这对我来说意味着在构建过程中出现了问题。我对代码访问安全性的工作方式不够了解,无法确定确切的问题所在。必须有一部分流程查看CodeAccessSecurityAttributes并尝试将SecurityAction应用于代码。我假设它会为程序集构建某种元数据,并以某种有序的方式执行此操作,以便在通过Program类之后才能看到可选参数。然后,在构建过程中必须以某种方式使用该元数据,这就是您看到失败的地方。如需更多详细信息,我们将需要希望了解编译器的人,例如Eric,进行阐述。我建议按照其中一个评论建议在connect.microsoft.com上提交它,因为似乎是由遍历顺序引起的错误。

要是所有的缺陷报告都能像这样研究得这么充分、写得这么好就好了! - Rick Sladkey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接