9

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.

Community
  • 1
  • 1
Steven Jeuris
  • 18,274
  • 9
  • 70
  • 161

3 Answers3

9

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.

jason
  • 236,483
  • 35
  • 423
  • 525
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 1
    _"Can I ask why you want to know this stuff?"_ 1. Because it's fun and instructive! :) 2. I was writing an aspect using [PostSharp](http://www.sharpcrafters.com/) which at a certain point had to know about the backing field delegate. I figured that might be a problem and was able to use another approach. – Steven Jeuris Mar 23 '12 at 23:20
  • 3. As I didn't see any problems raised with e.g. [this answer](http://stackoverflow.com/questions/3783267/how-to-get-a-delegate-object-from-an-eventinfo/3783491#3783491) I figured it might be worthwhile asking it specifically. I'm starting to see a correlation between my questions and the magic trio that answer them. Thanks a lot! :) I also notice I should really start reading through that C# spec. Interesting stuff! – Steven Jeuris Mar 23 '12 at 23:21
  • And 100% full disclosure, I'm writing an aspect which eliminates the need for me to write _"delegate {}"_ when creating events, [hence my previous question](http://stackoverflow.com/q/9823457/590790). :) I just get a kick out of this sort of stuff. Even if it _might_ be overkill, it's a great way to learn. :) – Steven Jeuris Mar 23 '12 at 23:30
  • If one declares a compiler-generated event `Foo`, is there any situation, other than the special-case handling for `+=` and `-=`, in which a standards-compliant compiler could interpret `Foo` as anything other than the name of the backing field? If I had my druthers, the identifier associated with an `event` would only be usable for five operations: add, remove, set to null, invoke-if-not-null (no-op if null), and compare against null. For anything else, one would have to explicitly refer to the identifier used for the backing field. Nonetheless, since the spec isn't written that way, ... – supercat May 14 '12 at 21:30
  • ...how could a standards-compliant compiler use anything other than the event name as a backing field? – supercat May 14 '12 at 21:31
6

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.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Okay, thanks! So using `GetField( eventName )` to find the backing delegate would only work for assemblies compiled with the 'correct' compiler. It might be interesting to know how many compilers use a different name. – Steven Jeuris Mar 23 '12 at 22:55
  • 1
    @StevenJeuris: Not just that - it assumes that the event is being implemented with a field-like event in the first place. As Marc says, if you need to go behind the implementation's back, you're doing it wrong :( – Jon Skeet Mar 23 '12 at 23:00
5

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.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • _"you probably aren't using events correctly"_ ... the world of aspects is a wonderful place. :) But you are right, I did already manage to write my aspect differently, eliminating the need to get the backing field. I was wondering whether or not I should keep `GetFieldInfo()` in my library, as it felt dangerous. It's gone now. ;p – Steven Jeuris Mar 23 '12 at 23:00