Is the backing field of a compiler generated event

2019-02-12 19:20发布

问题:

C# allows us to create custom event accessors.

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

If you don't specify them, the compiler creates them for you. C# language spec:

When compiling a field-like event, the compiler automatically creates storage to hold the delegate, and creates accessors for the event that add or remove event handlers to the delegate field.

The decompiled source code using dotPeek for a simple public event Action Public; looks as follows:

  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);
    }
  }

Noteworthy is that the field and the event use the same name. This has led some people to conclude that you can find information about the backing field during reflection by looking up the field in the class with the same name as the event. I implemented this as follows:

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 );
}

This works, but raises the question: Is the backing field of a compiler generated event always guaranteed to use the same name as the event?

It's not possible to create custom event accessors which access a delegate with the same name using Visual Studio. This results in the message: "Member with the same name is already declared." I am wondering whether you could conclude that any event for which no backing delegate with the same name is available is an event with custom accessors.

回答1:

Is the backing field of a compiler generated event always guaranteed to use the same name as the event?

Jon and Marc are entirely correct to answer "No".

This is an undocumented implementation detail of the compiler, explicitly noted by the specification as such, and subject to change at any time.

In practice, this is unlikely to change. We use the fact that the field and the event have the same name as the simplest possible way to associate them logically with each other in the compiler.

It's not possible to create custom event accessors which access a delegate with the same name using Visual Studio. This results in the message: "Member with the same name is already declared."

Correct.

I am wondering whether you could conclude that any event for which no backing delegate with the same name is available is an event with custom accessors.

I would not be comfortable making such a conclusion. You might be able to make that conclusion if you knew that the assembly in question had been emitted from the C# compiler. But we are not the only game in town when it comes to emitting assemblies. You can do some pretty weird stuff with ILDASM.

Can I ask why you want to know this stuff? I agree with Marc; if you are accessing a field via Reflection, you're probably doing it wrong. You should be able to access the field within the class no problem (because it is just a private field) and from outside the class, you have no business looking at the private implementation details of another class. It is particularly egregious to use reflection to do an end-run around the thread safety imposed by the accessors. Those accessors are there for your protection; don't run around them.



回答2:

No - from the C# 4 spec (section 10.8.1):

Within the class X, references to Ev are compiled to reference the hidden field _Ev instead. The name “_Ev” is arbitrary; the hidden field could have any name or no name at all.

So while source-code compatibility is guaranteed, there's no guarantee about the name of the generated field. (In practice, I wouldn't expect this to change any time soon in the MS compiler - but it's not guaranteed, so you shouldn't make assumptions.)



回答3:

The implementation of an event is a compiler implementation detail, and differs between compilers (MS c# 4 has a different implementation to MS c# < 4, and even the MS and ECMA specifications disagree).

Personally, I'd say: if you need to access the backing field via reflection, you probably aren't using events correctly.