使用通用类的参数作为事件类型

时间:2019-08-04 15:42:02

标签: c# generics events delegates

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

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

好的…C#≥7.3 allows Delegate as base class constraint。我们来使用它:

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

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

WTH ???

1 个答案:

答案 0 :(得分:2)

尽管我在C#规范中找不到记录的限制,但我可以看到在C#/ CLR中支持此类事件至少有两个问题,都与引发它的方式有关。

首要困难:语言

C#仅允许从声明该事件的类型内引发事件。但是,如果您的通用类甚至不知道其T的参数数量,引发事件的代码应该是什么样?

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

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

当然,您可以使MyClass抽象,并说指定T类型的继承者将引发该事件。但是,我认为这将是一个非常不一致的语言设计。

第二个困难:在编译器中

CLR实现运行时泛型。这意味着,对于符合通用约束的任何T,编译器必须生成在运行时应良好的IL。

引发事件基本上是调用存储在事件字段中的委托。编译器应生成大致包括以下步骤的IL:

  • 将委托对象引用推入堆栈
  • 推送参数1
  • 推送参数2
  • ....
  • 推送参数N
  • 呼叫代表的调用方法

如果委托人不是void,则需要执行其他步骤:

  • 从堆栈中弹出返回值,并可能将其存储在字段或局部变量中

如您所见,生成的IL严格取决于参数的数量以及委托是否为void。因此,这样的IL对任何Delegate都是不利的。

相反

具有带有通用参数的事件委托完全可以,例如:

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

因为在编译时知道参数的数量以及委托是否为void。在这种情况下,可以生成对任何KV都适用的相同的IL指令集。在IL中,KV被生成为类型占位符,CLR能够在运行时对其进行解析。