6

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

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。”

我想知道为什么这不起作用。如果是的话会带来什么问题?或者我错过了什么让它工作?

提前致谢!

4

2 回答 2

7

“in”指定逆变,对吧?

是的。

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

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

interface ISomeBaseInterface
{
}

interface ISomeInterface : ISomeBaseInterface
{
}

并假设handler采取ISomeBaseInterface而不是SomeDerivedClass

static void handler(object sender, ISomeBaseInterface e) 

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

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

class SomeOtherDerivedClass : ISomeInterface
{
}

如果您能够注册void handler(object sender, SomeDerivedClass e)该事件,则该处理程序最终会被调用SomeOtherDerivedClass,这是行不通的。

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

更新:您评论:

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

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

class SomeClass
{
    private Delegate handlers;

    public delegate void SomeEventDelegate<in T>(object sender, T data);

    public void AddSomeEventHandler<T>(SomeEventDelegate<T> handler)
    {
        this.handlers = Delegate.Combine(this.handlers, handler);
    }

    protected void OnSomeEvent<T>(T data)
    {
        if (this.handlers != null)
        {
            foreach (SomeEventDelegate<T> handler in
                this.handlers.GetInvocationList().OfType<SomeEventDelegate<T>>())
            {
                handler(this, data);
            }
        }
    }
}
于 2012-03-31T15:48:03.850 回答
2

委托逆变的一个主要烦恼是,虽然类型为 eg 的委托Action<Fruit>可能被传递给期望 的例程Action<Banana>,但尝试组合两个实际类型为Action<Fruit>并且Action<Banana>将失败的委托 *即使两个委托都具有“编译时”类型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>,然后再从事件中添加或删除它。

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

    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,并允许将此类类型的实例添加到合适的委托类型的变量或字段中或从中减去;这种加法或减法将以线程安全的方式完成,因此可以在事件添加/删除方法中使用这些方法而无需额外锁定。

于 2012-12-14T18:47:10.177 回答