EventHandlers和Covariance

时间:2012-03-31 14:11:48

标签: c# events delegates variance

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

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<in T>(object sender, T data);
        public event SomeEventDelegate<ISomeInterface> SomeEvent;

    }

    interface ISomeInterface
    {
    }

    class SomeDerivedClass : ISomeInterface
    {
    }
}

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

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

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

提前致谢!

2 个答案:

答案 0 :(得分:6)

  

&#34;在&#34;指定反方差,对吧?

  

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

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

interface ISomeBaseInterface
{
}

interface ISomeInterface : ISomeBaseInterface
{
}

假设handler取代ISomeBaseInterface而不是SomeDerivedClass

static void handler(object sender, ISomeBaseInterface e) 

然后new SomeClass().SomeEvent += handler就可以了。

这就是原始代码不安全的原因:当SomeClass提升SomeEvent时,它可能会传递实现{{1}的任何作为ISomeInterface参数。例如,它可以传递data的实例,但它也可以传递

的实例
SomeDerivedClass

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

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

更新:您发表了评论:

  

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

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

event

答案 1 :(得分:2)

代表性逆转的一个主要烦恼是,虽然是一个类型的代表,例如可以将Action<Fruit>传递给期望Action<Banana>的例程,尝试组合实际类型为Action<Fruit>Action<Banana>的两个委托将失败*,即使两个委托都有“编译” -time“type Action<Banana>。为了解决这个问题,我建议使用如下方法:

    static T As<T>(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<string>的代表的事件,那么可以使用上述方法来转换,例如在将事件添加或删除之前,已将Action<object>传入“真实”Action<string>

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

    static void AppendTo<T>(this Delegate newDel, ref T baseDel) where T : class
    {
        newDel = (Delegate)(Object)newDel.As<T>();
        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<T>(this Delegate newDel, ref T baseDel) where T : class
    {
        newDel = (Delegate)(Object)newDel.As<T>();
        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派生的类型的扩展方法出现,并允许在合适的委托类型的变量或字段中添加或减去此类类型的实例;这样的加法或减法将以线程安全的方式完成,因此可以在事件添加/删除方法中使用这些方法而无需额外的锁定。