代码之家  ›  专栏  ›  技术社区  ›  Zack ISSOIR

为什么在.net生态系统中,teventargs不是标准事件模式下的反变种?

  •  0
  • Zack ISSOIR  · 技术社区  · 6 年前

    当进一步了解.NET中的标准事件模型时,我发现在C中引入泛型之前,处理事件的方法由此委托类型表示:

    //
    // Summary:
    //     Represents the method that will handle an event that has no event data.
    //
    // Parameters:
    //   sender:
    //     The source of the event.
    //
    //   e:
    //     An object that contains no event data.
    public delegate void EventHandler(object sender, EventArgs e);
    

    但是在C 2中引入泛型之后,我认为使用泛型来重写这个委托类型:

    //
    // Summary:
    //     Represents the method that will handle an event when the event provides data.
    //
    // Parameters:
    //   sender:
    //     The source of the event.
    //
    //   e:
    //     An object that contains the event data.
    //
    // Type parameters:
    //   TEventArgs:
    //     The type of the event data generated by the event.
    public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
    

    我有两个问题:

    首先,为什么 特文斯塔格 类型参数 制造的 反变体 ?

    如果我没有弄错,建议将在委托签名反变量中显示为形式参数的类型参数和将在委托签名协变中作为返回类型的类型参数。

    在约瑟夫·阿尔巴哈里的书中,简而言之,我引用了:

    如果定义的是泛型委托类型,则最佳做法是:

    • 将仅用于返回值的类型参数标记为协变(out)。
    • 将任何仅用于参数的类型参数标记为逆变(in)。

    这样做可以通过尊重自然地进行转换 类型之间的继承关系。

    第二个问题:为什么没有通用约束来强制TEVARARGS从 系统事件参数 ?

    如下:

    public delegate void EventHandler<TEventArgs>  (object source, TEventArgs e) where TEventArgs : EventArgs; 
    

    提前谢谢。

    编辑以澄清第二个问题:

    似乎是对teventargs的一般约束( 其中teventargs:eventargs 曾经有过,它被微软移除了,所以看起来设计团队意识到它并没有太多实用意义。

    我编辑了我的答案,包括一些截图

    .NET reference source

    enter image description here

    0 回复  |  直到 6 年前
        1
  •  38
  •   Wai Ha Lee captain-yossarian from Ukraine    6 年前

    首先,在对问题的评论中解决一些问题:我通常会极力回避“为什么不”的问题,因为很难找到简洁的理由 世界上每个人都选择不做这项工作 ,因为 默认情况下并非所有工作都完成 . 相反,你必须找到一个理由 工作,从 其他工作 这样做就不那么重要了。

    此外,这种形式的“为什么不”问题,询问在特定公司工作的人的动机和选择,可能只能由作出决定的人来回答,而这些人可能不在这里。

    但是,在这种情况下,我们可以对我通常的结束“为什么不”问题的规则做一个例外,因为 这个问题说明了委托协方差的一个重要点,这是我以前从未写过的。

    我没有决定保持事件委托的非变体,但如果我能够这样做,我会保持事件委托的非变体,原因有二。

    第一点纯粹是“鼓励良好做法”的观点。事件处理程序通常是专为处理特定事件而构建的,并且没有充分的理由让它比已经使用签名中不匹配的委托更容易,即使这些错配可以通过方差来处理。事件处理程序在各个方面都与它应该处理的事件完全匹配,这使我更加确信开发人员知道他们在构建事件驱动工作流时在做什么。

    这是个很弱的理由。更强烈的原因也是更悲伤的原因。

    我们知道,泛型委托类型可以在它们的返回类型和它们的参数类型中的逆变类型协变,我们通常在赋值兼容性的上下文中考虑方差。也就是说,如果我们有 Func<Mammal, Mammal> 在手上,我们可以把它分配给一个类型的变量 Func<Giraffe, Animal> 要知道,潜在的功能总是需要哺乳动物——因为现在它只会得到长颈鹿——而且总是会返回动物——因为它返回哺乳动物。

    但我们也知道,委托可以加在一起;委托是不可变的,因此将两个委托加在一起会产生第三个委托;和是求和的顺序组合。

    类字段事件使用委托求和实现;这就是为什么向事件添加处理程序表示为 += . (我不太喜欢这种语法,但我们现在已经习惯了。)

    尽管这两个特性彼此独立地工作得很好,但它们在组合中工作得很差。当我实现委托差异时,我们的测试在短时间内发现clr中存在许多关于委托添加的错误,其中由于启用差异的转换,基础委托类型不匹配。这些错误从clr 2.0开始就存在了,但是直到c 4.0,没有主流语言暴露这些错误,也没有为它们编写测试用例,等等。

    可悲的是,我不记得这些虫子的复制者是什么,这是十二年前的事,我不知道我是否还有任何笔记被放在磁盘上的某个地方。

    我们当时与clr团队合作,试图在下一个版本的clr中解决这些bug,但与它们的风险相比,它们的优先级不够高。很多类型像 IEnumerable<T> IComparable<T> 在这些版本中有很多不同的版本, Func Action 类型,但 很难把两个不匹配的加在一起 芬克 使用变量转换 . 但是对于事件代理来说,它们在生命中的唯一目的是被添加在一起;它们将一直被添加在一起,如果它们是变体,那么将有可能将这些错误暴露给大量用户。

    在C 4事件后不久,我就失去了对这些问题的了解,我真的不知道这些问题是否得到了解决。试着将一些不匹配的委托以不同的组合组合在一起,看看有没有什么不好的事情发生!

    所以这是一个很好但很不幸的理由 使事件委托在C 4.0版本的时间范围内有所变化。我不知道还有没有好的理由。你得问问CLR小组的人。