2
class MyClass<T> {
    public event T MyEvent;
}

Error: CS0066 'MyClass<T>.MyEvent': event must be of a delegate type.

Okay… C# ≥7.3 allows Delegate as base class constraint. Let's use that:

class MyClass<T> where T: Delegate {
    public event T MyEvent;
}

Error: CS0066 'MyClass<T>.MyEvent': event must be of a delegate type.

WTH???

Sasha
  • 3,599
  • 1
  • 31
  • 52
  • looks like a duplicate of this: https://stackoverflow.com/questions/3126886/how-to-declare-generic-event-for-generic-delegate-in-c-sharp – felix-b Aug 04 '19 at 15:53
  • @felix-b, not really. In that question IIUC the event has predetermined shape (i.e.: two parameters, second one of type `FileUploadType`) and only the first parameter is `T` (I don't understand whether the author wanted first parameter to accept anything or just to have type corresponding to outer generic class parameter, but anyway that's not my case). In my case I want the event shape itself to depend on the outer generic class parameter: i.e. in my case `T` could be 0-parameter delegate, 2-parameter delegate or etc. – Sasha Aug 04 '19 at 16:07
  • 1
    You're right. Interesting, the [Events section](https://github.com/dotnet/csharplang/blob/master/spec/classes.md#events) in C# spec doesn't specify anything special regarding event delegate type. Probably it's hard or impossible to generate IL that invokes such event, maybe because it isn't known whether the delegate is void or returns a value. Yet, I'd expect such limitation to be documented. – felix-b Aug 04 '19 at 16:14

1 Answers1

2

Though I can't find a documented limitation in the C# spec, I can see at least two problems with supporting such an event in C#/CLR, both related to how it is raised.

First difficulty: in the language

C# only allows raising an event from within the type that declares it. But if your generic class doesn't even know the number of parameters it T, what should the code that raises the event look like?

class MyClass<T> where T: Delegate 
{
    public event T MyEvent;

    public void DoSomething()
    {
        // raise MyEvent here
        MyEvent(/* what goes here? */);
    }
}

Of course, you can make MyClass abstract and say that inheritors that specify the type of T would raise the event. However, this would be quite an inconsistent language design, to my opinion.

Second difficulty: in the compiler

CLR implements runtime generics. This means, that compiler must generate IL that should be good at runtime for any T that meets the generic constraints.

Raising an event is basically invoking a delegate that's stored in the event field. The compiler should generate IL that roughly includes these steps:

  • push delegate object reference onto the stack
  • push argument 1
  • push argument 2
  • ....
  • push argument N
  • call delegate's Invoke method

If the delegate isn't void, an additional step is required:

  • pop return value from the stack and possibly store it in a field or a local variable

As you can see, the generated IL strictly depends on the number of arguments and whether the delegate is void. Therefore, such IL cannot be good for any Delegate.

In contrast

Having event delegate with generic parameters is perfectly OK, such as:

delegate void MyEventHandler<K, V>(K key, V value);

because the number of the parameters and whether the delegate is void is known at compile time. In this case the same set of IL instructions can be generated that is good for any K and V. In the IL, K and V are generated as type placeholders, which CLR is capable of resolving at runtime.

felix-b
  • 8,178
  • 1
  • 26
  • 36
  • «Of course, you can make MyClass abstract and say that inheritors that specify the type of T would raise the event» — yes, that's exactly how I originally intended to use it. However finally I decided to make a `void (T param)` event with generic type `T`. Still it arouses theoretical interest anyway. – Sasha Aug 04 '19 at 18:04