简体   繁体   English

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

[英]Using parameter of generic class as type of event

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

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

Okay… C# ≥7.3 allows Delegate as base class constraint .好的... C# ≥7.3 允许Delegate作为基类约束 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 .错误: CS0066 'MyClass<T>.MyEvent': event must be of a delegate type

WTH???什么???

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.尽管我在 C# 规范中找不到记录在案的限制,但我可以看到在 C#/CLR 中支持此类事件至少存在两个问题,这两个问题都与它的引发方式有关。

First difficulty: in the language第一难点:语言

C# only allows raising an event from within the type that declares it. C# 只允许从声明它的类型中引发事件。 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?但是,如果您的泛型类甚至不知道它T的参数数量,那么引发事件的代码应该是什么样的?

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.当然,您可以使MyClass抽象并说指定T类型的继承者将引发事件。 However, this would be quite an inconsistent language design, to my opinion.然而,在我看来,这将是一种非常不一致的语言设计。

Second difficulty: in the compiler第二个难点:在编译器中

CLR implements runtime generics. CLR 实现运行时泛型。 This means, that compiler must generate IL that should be good at runtime for any T that meets the generic constraints.这意味着,编译器必须为满足通用约束的任何T生成在运行时应该很好的 IL。

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:编译器应生成大致包括以下步骤的 IL:

  • push delegate object reference onto the stack将委托对象引用压入堆栈
  • push argument 1推论点 1
  • push argument 2推论点 2
  • .... ....
  • push argument N推论 N
  • call delegate's Invoke method调用委托的 Invoke 方法

If the delegate isn't void , an additional step is required:如果委托不是void ,则需要额外的步骤:

  • 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 .如您所见,生成的 IL 严格取决于参数的数量以及委托是否为void Therefore, such IL cannot be good for any Delegate .因此,这种 IL 对任何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.因为参数的数量以及委托是否为void在编译时是已知的。 In this case the same set of IL instructions can be generated that is good for any K and V .在这种情况下,可以生成适用于任何KV的同一组 IL 指令。 In the IL, K and V are generated as type placeholders, which CLR is capable of resolving at runtime.在 IL 中, KV作为类型占位符生成,CLR 能够在运行时解析它们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM