【问题标题】:Using parameter of generic class as type of event使用泛型类的参数作为事件类型
【发布时间】:2019-08-04 15:42:02
【问题描述】:
class MyClass<T> {
    public event T MyEvent;
}

错误:CS0066 'MyClass&lt;T&gt;.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&lt;T&gt;.MyEvent': event must be of a delegate type

WTH???

【问题讨论】:

  • 看起来像这样的副本:stackoverflow.com/questions/3126886/…
  • @felix-b,不是真的。在那个问题 IIUC 中,事件具有预定的形状(即:两个参数,第二个参数类型为 FileUploadType),只有第一个参数是 T(我不明白作者是否希望第一个参数接受任何东西或只是为了具有对应于外部泛型类参数的类型,但无论如何这不是我的情况)。在我的情况下,我希望事件形状本身依赖于外部通用类参数:即在我的情况下 T 可以是 0 参数委托、2 参数委托等。
  • 你是对的。有趣的是,C# 规范中的 Events section 没有指定任何关于事件委托类型的特殊内容。可能很难或不可能生成调用此类事件的 IL,可能是因为不知道委托是无效还是返回值。然而,我希望这种限制会被记录在案。

标签: c# generics events delegates


【解决方案1】:

虽然我在 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 能够在运行时解析。

【讨论】:

  • «当然,您可以将 MyClass 抽象化,并说指定 T 类型的继承者会引发事件» — 是的,这正是我最初打算使用它的方式。不过最后我决定用泛型类型T 制作一个void (T param) 事件。无论如何,它仍然引起了理论上的兴趣。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-09-28
  • 1970-01-01
  • 1970-01-01
  • 2015-08-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多