There are two common use cases where you actually need to write add and remove manually: Explicit implementations and forwarding events to each other.
class C : Interface {
private EventHandler @event;
event EventHandler Interface.Event {
add { @event += value; }
remove { @event -= value; }
}
public event EventHandler Event {
add { obj.Event += value; }
remove { obj.Event -= value; }
}
}
This can be a source of bugs like using += in remove and these seem to be complete boilerplate which auto events are meant to prevent. It'd be nice to be able to define these events to be auto implemented,
class C : Interface {
private EventHandler @event;
event EventHandler Interface.Event => @event;
public event EventHandler Event => obj.Event;
}
The expression in front of => must be a valid lvalue. Related: #1276.
I'll also note that the code emitted by the C# compiler in the default event accessors is quite different than what results if you manually use += or Delegate.Combine. Having a syntax which would allow for explicit implementation as well as proper thread-safe event subscription would be nice.
Is a separate syntax really necessary for forwarding, though? Seems that catching an accidental += in a remove accessor is something an analyzer could catch.
@HaloFour It feels missing rather than a separate syntax,
object Property => obj.Property;
object Method() => obj.Method();
event Handler Event => obj.Event;
It doesn't _return_ anything per se, but it's consistent with other constructs.
@alrz The behavior wouldn't be consistent, though. What do you propose the compiler emit if obj.Event is an event, or a property that happens to return a Handler instance? What if that property is readonly?
@HaloFour I'm thinking that it _must_ be an event with a compatible delegate type. The compiler would emit add and remove just like the example above.
I believe that the reason that C# doesn't allow for auto-implemented events in explicitly implemented interfaces is that it provides no way to then raise that event:
public class Foo : IFoo {
event Handler IFoo.Event;
protected void OnEvent() {
// ?
}
}
But if you could combine the two you could potentially do this.
public class Foo : IFoo {
private event Handler InternalEvent;
event Handler IFoo.Event => this.InternalEvent;
protected void OnEvent() {
this.InternalEvent?.Invoke(this, EventArgs.Empty);
}
}
I honestly don't think I've seen event forwarding or explicit event implementation all that frequently, though.
@HaloFour Well, that settles that. Re "I honestly don't think I've seen event forwarding or explicit event implementation all that frequently, though." It is when you work with WPF or any event based framework. I'm not a fan of events in their current shape, perhaps if they could expose themselves as observables, there might be hope. But currently I'm stuck with it.
PS: Now I see how it can be useful to forward to a field because InternalEvent doesn't really need to be an event here.
@alrz What about WPF makes it necessary to explicitly implement or forward events? I've never found myself having to do either when I worked on WPF or Silverlight. That's just my experience, though. I went to the effort of writing helper methods that duplicates the interlocked loop that the C# compiler emits for the default event accessor methods. Only ended up using them once which was later removed during a refactor.
I agree about events and observables, it would be nice if C# offered a simple syntax to interop between the two.
What about WPF makes it necessary to explicitly implement or forward events?
In proper implementation of ICommand it is recommended to forward CanExecuteChanged events to CommandManager.RequerySuggested. Sometimes It's also nice to forward some events on your custom control to "inner" nested controls in your template.
I don't think it's worth putting any more design into C# events, as they are now much less useful than they once were (with Tasks, Observables, framework-specific event types).
@MgSam This is a part of the language anyways (without any particular replacement like delegate vs lambda). I don't agree that they are less useful or deprecated, a lot of "modern" frameworks depend on this, because it's idiomatic C# and has proper support in CLR. Also, they can be resurrected through observable interop to be more useful in a wide variety of use cases.
Since => is being considered for other constructs as well (per @gafter's comment), it'd be nice to also support it for events.
public class Foo : IFoo {
private Handler @event;
event Handler IFoo.Event => @event;
}
PS: Ironically, that was proposed by yourself (#7881).
@alrz Right, but I think this proposal is slightly more complex than my other proposal because this involves new semantics to the expression body form that don't exist anywhere else in the language where expression bodies are used.
I write properties every day, whereas I can count on one hand the number of times I've explicitly written add and remove. I personally don't feel it's worth the language design cost, but I'm indifferent as to the feature itself.
I'm talking about consistency. I don't see myself writing expression-bodied ctors or dtors either, but here we are. You're right about its semantics, I'll wait for the team to decide whether it's a good idea or not, conciseness is quite nice, though.
I have already felt the lack of both expression-bodied events, and expression-bodied event accessors. This comes up not infrequently with explicit implementations which require you to write less pleasant code.
Any time you explicitly implement an event, you are forced to implement add and remove accessors. In my use cases the great benefit here would be in forwarding the event to a _field_, so that the default thread-safe CAS-based handlers get compiled in instead of += and -=. Today I use this boilerplate:
``` c#
private event EventHandler Foo; // Could just as easily be declared as a field
event EventHandler IBar.Foo
{
add { ThreadSafe.InterlockedAdd(ref Foo, value); }
remove { ThreadSafe.InterlockedRemove(ref Foo, value); }
}
And that's assuming I got the implementation right, which also isn't something everyone should have to maintain on their own:
``` c#
public static TDelegate InterlockedAdd<TDelegate>(ref TDelegate delegateField, TDelegate addend) where TDelegate : class
{
var originalValue = Volatile.Read(ref delegateField); // If stale, Volatile.Read prevents an unnecessary call to modifier.
while (true)
{
var newValue = (TDelegate)(object)Delegate.Combine((Delegate)(object)originalValue, (Delegate)(object)addend);
var preCompareValue = Interlocked.CompareExchange(ref delegateField, newValue, originalValue);
if (preCompareValue == originalValue) return newValue;
originalValue = preCompareValue;
}
}
expression-bodied accessors make this slightly less verbose, and thread-safety can be generated though replaced members.