EventHandlers和Covariance

我一直在尝试创建一个通用事件。 基本上它应该是这样的:

namespace DelegateTest { class Program { static void Main(string[] args) { var lol = new SomeClass(); lol.SomeEvent += handler; } static void handler(object sender, SomeDerivedClass e) { } } class SomeClass { public delegate void SomeEventDelegate(object sender, T data); public event SomeEventDelegate SomeEvent; } interface ISomeInterface { } class SomeDerivedClass : ISomeInterface { } } 

我想允许用户传递任何委托,其中第二个参数派生自“ISomeInterface”。

“in”指定反差,对吗? 这意味着如果API期望更通用的东西,你可以传递更具体的东西(在我的基础“ISomeInterface”将是通用的,而我的“SomeDerivedClass”将是特定的。)但是,我被告知我的编译器“方法处理程序没有重载匹配DelegateTest.SomeClass.SomeEventDelegate。”

我想知道为什么这不起作用。 如果是这样会导致什么问题? 或者我错过了一些工作的东西?

提前致谢!

“in”指定反差,对吗?

是。

这意味着如果API期望更通用的东西,你可以传递更具体的东西(在我的基础“ISomeInterface”将是通用的,而我的“SomeDerivedClass”将是特定的)。

否。委托反演允许委托引用具有比委托类型更少派生的参数类型的方法。 例如,假设ISomeInterface有一个基接口:

 interface ISomeBaseInterface { } interface ISomeInterface : ISomeBaseInterface { } 

假设handlerISomeBaseInterface而不是SomeDerivedClass

 static void handler(object sender, ISomeBaseInterface e) 

然后new SomeClass().SomeEvent += handler会起作用。

这就是原始代码不是类型安全的原因:当SomeClass引发SomeEvent ,它可能会将实现ISomeInterface 任何东西作为data参数传递。 例如,它可以传递SomeDerivedClass的实例,但它也可以传递一个实例

 class SomeOtherDerivedClass : ISomeInterface { } 

如果您能够在事件中注册void handler(object sender, SomeDerivedClass e) ,则最终将使用SomeOtherDerivedClass调用该处理程序,这不起作用。

总之,您可以注册比事件类型更通用的事件处理程序,而不是更具体的事件处理程序。

更新:你评论说:

好吧,我实际上想要遍历列表并检查类型。 因此,如果要使用类型为letOtherDerivedObject的数据对象触发事件,则程序将遍历订阅该事件的方法列表,直到找到与该签名匹配的方法(对象,SomeOtherDerivedObject)。 所以事件本身只会用于存储,而不是实际调用代理。

我不认为C#允许您声明一个适用于任意委托类型的event 。 以下是编写添加事件处理程序并调用它们的方法的方法:

 class SomeClass { private Delegate handlers; public delegate void SomeEventDelegate(object sender, T data); public void AddSomeEventHandler(SomeEventDelegate handler) { this.handlers = Delegate.Combine(this.handlers, handler); } protected void OnSomeEvent(T data) { if (this.handlers != null) { foreach (SomeEventDelegate handler in this.handlers.GetInvocationList().OfType>()) { handler(this, data); } } } } 

代表逆转的一个主要烦恼是,虽然类型的代表例如Action可以传递给期望Action的例程,但是尝试组合两个代理,其实际类型是ActionAction即使两个代表都有“编译时”类型Action也会失败*。 为了解决这个问题,我建议使用如下方法:

  static T As(this Delegate del) where T : class { if (del == null || del.GetType() == typeof(T)) return (T)(Object)del; Delegate[] invList = ((Delegate)(Object)del).GetInvocationList(); for (int i = 0; i < invList.Length; i++) if (invList[i].GetType() != typeof(T)) invList[i] = Delegate.CreateDelegate(typeof(T), invList[i].Target, invList[i].Method); return (T)(Object)Delegate.Combine(invList); } 

给定委托和委托类型,此方法将检查传入的委托的类型是否与指定的类型精确匹配; 如果没有,但原始委托中的方法具有指定类型的正确签名,则将创建具有必要特征的新委托。 请注意,如果在两个不同的场合,此函数传递的参数类型不是正确类型但相互比较相等,则此方法返回的委托也将相互比较。 因此,如果有一个事件应该接受Action类型的委托,那么可以使用上面的方法在添加之前将传入的Action转换为“真正的” Action将其从活动中删除。

如果要从正确的委托类型的字段添加或减去传入的委托,如果使用以下方法,则可以改进类型推断和智能感知行为:

  static void AppendTo(this Delegate newDel, ref T baseDel) where T : class { newDel = (Delegate)(Object)newDel.As(); T oldBaseDel, newBaseDel; do { oldBaseDel = baseDel; newBaseDel = (T)(Object)Delegate.Combine((Delegate)(object)oldBaseDel, newDel); } while (System.Threading.Interlocked.CompareExchange(ref baseDel, newBaseDel, oldBaseDel) != oldBaseDel); } static void SubtractFrom(this Delegate newDel, ref T baseDel) where T : class { newDel = (Delegate)(Object)newDel.As(); T oldBaseDel, newBaseDel; do { oldBaseDel = baseDel; newBaseDel = (T)(Object)Delegate.Remove((Delegate)(object)oldBaseDel, newDel); } while (System.Threading.Interlocked.CompareExchange(ref baseDel, newBaseDel, oldBaseDel) != oldBaseDel); } 

这些方法将作为派生自Delegate类型的扩展方法出现,并允许将这些类型的实例添加到合适的委托类型的变量或字段中或从中减去这些类型的变量或字段。 这样的加法或减法将以线程安全的方式完成,因此可以在事件添加/删除方法中使用这些方法而无需额外的锁定。