【问题标题】:In C#, are event handler arguments contravariant?在 C# 中,事件处理程序参数是逆变的吗?
【发布时间】:2010-05-14 10:44:31
【问题描述】:

如果我有一个通过(例如)FrobbingEventArgs 引发事件的类,我是否可以使用采用 EventArgs 的方法来处理它?

这里有一些代码:

class Program
{
   static void Main(string[] args)
   {
      Frobber frobber = new Frobber();
      frobber.Frobbing += FrobberOnFrobbing;
      frobber.Frob();
   }

   private static void FrobberOnFrobbing(object sender,
       EventArgs e)
   {
      // Do something interesting. Note that the parameter is 'EventArgs'.
   }
}

internal class Frobber
{
   public event EventHandler<FrobbingEventArgs> Frobbing;
   public event EventHandler<FrobbedEventArgs> Frobbed;

   public void Frob()
   {
      OnFrobbing();

      // Frob.

      OnFrobbed();
   }

   private void OnFrobbing()
   {
      var handler = Frobbing;
      if (handler != null)
         handler(this, new FrobbingEventArgs());
   }

   private void OnFrobbed()
   {
      var handler = Frobbed;
      if (handler != null)
         handler(this, new FrobbedEventArgs());
   }
}

internal class FrobbedEventArgs : EventArgs { }
internal class FrobbingEventArgs : EventArgs { }

我问的原因是 ReSharper 似乎对 XAML 中的等效项有问题(看起来像),我想知道这是 ReSharper 中的错误,还是我对 C# 的理解有误。

【问题讨论】:

标签: c# events contravariance


【解决方案1】:

也许协变不是这个词

关闭。您正在寻找的词是逆变。将方法组转换为委托类型在返回类型中是协变,在形式参数类型中是逆变

这是我关于这个主题的博客文章:

https://docs.microsoft.com/en-us/archive/blogs/ericlippert/covariance-and-contravariance-in-c-part-three-method-group-conversion-variance

如果我有一个通过(例如)FrobbingEventArgs 引发事件的类,我是否可以使用采用 EventArgs 的方法来处理它?

嗯,你试过了,它奏效了,很明显是的。有关规范中的理由,请参阅标题为“方法组转换”的部分。

【讨论】:

  • Eric,感谢您的更正——将“协变”更改为“逆变”。也感谢你的理由。
  • 这没有问题并且工作正常,因为Main 中第二个代码行的方法组转换转换为完全相同的构造泛型类型。 (运行时类型等于编译时类型。)所以这也适用于 .NET 2.0。如果有人来这里搜索“逆变”,请注意以下几点(.NET 4.0):不要进行像public event Action&lt;FrobbingEventArgs&gt; Frobbing; 这样的事件,因为Action&lt;in T&gt; 是逆变的(in)。然后人们可以添加Action&lt;EventArgs&gt; 或类似的事件。如果其他订阅者使用不同的Action&lt;T&gt;,这将不起作用
【解决方案2】:

可以,但是当您访问事件处理程序中的 e 参数时,您将只能访问属于 EventArgs 基类的成员,除非您将其转换为派生类型。我认为这个词是多态的而不是协变的,而且 c# 中的所有类都是多态的,这是一种语言特性。

【讨论】:

  • FrobbedEventArgs 可以转换为 EventArgs 的事实是多态性,但我认为 EventHandler 类型的委托可以添加到 EventHandler 事件的事实是协变的。
  • @nikie 实际上,如果你look at EventHandler&lt;TEventArgs&gt;,你会看到它的泛型参数是不变的,而不是逆变的。它说EventHandler&lt;TEventArgs&gt;,而不是EventHandler&lt;in TEventArgs&gt;。所以在这种情况下,不可能像你说的那样将EventHandler&lt;EventArgs&gt; 添加到EventHandler&lt;FrobbedEventArgs&gt; 事件中。因此,即使静态目标方法对其参数有“较小”的要求,它也是方法组转换的逆变性,它创建了一个 EventHandler&lt;EventArgs&gt;
  • @JeppeStigNielsen:您注意到的行为是微软决定不让委托定义Combine 方法而是使用Delegate.Combine 的不幸结果。给定接口 IFoo、IBar 和 IBoth:IFoo,IBar,如果委托类型 Action&lt;T&gt; 定义了自己的组合方法,则 Action&lt;IBoth&gt;.Combine 可以接受 Action&lt;IFoo&gt;Action&lt;IBar&gt; 并产生 Action&lt;IBoth&gt; .尽管如此,Delegate.Combine 无法确定它可以使用哪种类型来组合 Action&lt;IFoo&gt;Action&lt;IBar&gt;
  • @supercat 我同意,Delegate.Combine(Delegate, Delegate) 无法确定类型。他们可以像你建议的那样解决它。另一种可能性是制作一个 generic 但仍然 static 组合方法,public static TDel Combine&lt;TDel&gt;(TDel x, TDel y) where TDel : Delegate { /* use typeof(TDel) in here */ }。在这种情况下,C# 中两个相同 compile-time 类型 TDel 的委托之间的 + 应该“转换”为对通用 Combine&lt;TDel&gt; 方法的调用。
  • @JeppeStigNielsen:类型系统无法处理委托类型的通用约束。此外,值得注意的是,虽然可以编写一个类型安全的 CombineAction&lt;T&gt; 方法,给定两个签名与 Action&lt;T&gt; 兼容的委托,Action&lt;T1&gt;Action&lt;T2&gt; 将产生一个 Action&lt;T&gt;,当调用时,将按顺序运行这两个操作,并且非类型安全版本将与签名与Action&lt;T&gt; 兼容的任何两个委托一起使用,在任何情况下,生成的委托都不会与...兼容。跨度>
【解决方案3】:

由于事件只能在声明它们的类中调用,因此您的派生类不能直接调用在基类中声明的事件。

您可以通过为事件创建受保护的调用方法来实现您想要的。通过调用此调用方法,您的派生类可以调用该事件。

为了获得更大的灵活性,调用方法通常被声明为虚拟的,这允许派生类覆盖它。这允许派生类拦截基类正在调用的事件,并可能对它们进行自己的处理。

你可以这样做:

protected void OnFrobbing(EventArgs e) 
   { 
      var handler = Frobbing; 
      if (handler != null) 
         handler(this, new e); 
   } 

或者:

protected virtual void OnFrobbing(EventArgs e) 
   { 
      var handler = Frobbing; 
      if (handler != null) 
         handler(this, new e); 
   } 

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多