是编译器生成的事件的后备字段,始终保证使用与该事件相同的名称

本文关键字:事件 编译器 字段 | 更新日期: 2023-09-27 18:28:55

C#允许我们创建自定义事件访问器。

Action _custom;
public event Action Custom
{
    add { _custom = (Action)Delegate.Combine( _custom, value ); }
    remove { _custom = (Action)Delegate.Remove( _custom, value ); }
}

如果您没有指定它们,编译器会为您创建它们。C#语言规范:

编译类似字段的事件时,编译器会自动创建存储以容纳委托,并为以下事件创建访问者向委托字段添加或删除事件处理程序。

对于一个简单的public event Action Public;,使用dotPeek反编译的源代码如下所示:

  private Action Public;
  public event Action Public
  {
    add
    {
      Action action = this.Public;
      Action comparand;
      do
      {
        comparand = action;
        action = Interlocked.CompareExchange<Action>(
                     ref this.Public, comparand + value, comparand);
      }
      while (action != comparand);
    }
    remove
    {
      Action action = this.Public;
      Action comparand;
      do
      {
        comparand = action;
        action = Interlocked.CompareExchange<Action>(
                    ref this.Public, comparand - value, comparand);
      }
      while (action != comparand);
    }
  }

值得注意的是字段和事件使用相同的名称。这导致一些人得出结论,通过在类中查找与事件同名的字段,可以在反射期间找到有关支持字段的信息。我实现如下:

public static FieldInfo GetFieldInfo( this EventInfo eventInfo )
{
    Contract.Requires( eventInfo != null );
    return eventInfo.DeclaringType.GetField(
        eventInfo.Name,
        BindingFlags.DeclaredOnly | BindingFlags.Instance |
            BindingFlags.Public | BindingFlags.NonPublic );
}

这是有效的,但提出了一个问题:编译器生成的事件的支持字段是否总是保证使用与事件相同的名称

无法使用Visual Studio创建访问同名委托的自定义事件访问器。这导致消息:"已声明具有相同名称的成员。"我想知道您是否可以得出结论,任何没有具有相同名称支持委托的事件都是具有自定义访问器的事件。

是编译器生成的事件的后备字段,始终保证使用与该事件相同的名称

编译器生成的事件的后备字段是否总是保证使用与该事件相同的名称?

乔恩和马克回答"不"是完全正确的。

这是编译器的一个未记录的实现细节,规范中明确指出了这一点,并且随时可能发生更改。

在实践中,这种情况不太可能改变。我们使用字段和事件具有相同名称这一事实,这是在编译器中将它们在逻辑上相互关联的最简单方法。

无法使用Visual Studio创建访问同名委托的自定义事件访问器。这将导致消息:"已声明具有相同名称的成员。"

正确。

我想知道您是否可以得出这样的结论:任何没有具有相同名称的支持委托的事件都是具有自定义访问器的事件。

我不愿意作出这样的结论。如果您知道有问题的程序集是从C#编译器发出的,那么您可能能够得出这个结论。但在排放组件方面,我们并不是城里唯一的游戏。你可以用ILDASM做一些非常奇怪的事情。

我能问你为什么想知道这些东西吗?我同意马克的观点;如果您通过"反射"访问字段,那么您可能做错了。您应该能够毫无问题地访问类内的字段(因为它只是一个私有字段),并且从类外,您不需要查看另一个类的私有实现细节使用反射来绕过访问器强加的线程安全性进行最终运行是特别令人震惊的那些访问者是为了保护您;不要围着他们跑。

否-来自C#4规范(第10.8.1节):

在类X中,对Ev的引用被编译为引用隐藏字段_Ev。名称"_Ev"是任意的;隐藏字段可以有任何名称,也可以根本没有名称。

因此,虽然保证了源代码的兼容性,但无法保证生成字段的名称。(在实践中,我预计MS编译器中不会很快改变这一点,但这并不能保证,所以你不应该做出假设。)

事件的实现是编译器实现细节,并且在编译器之间有所不同(MS c#4与MS c#<4有不同的实现,甚至MS和ECMA规范也不一致)。

就我个人而言,我想说:如果你需要通过反射来访问后台,你可能没有正确使用事件。