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
    {
    }
}

2番目のパラメーターが「ISomeInterface」から派生したデリゲートをユーザーが渡せるようにしたい。

「in」は逆分散を指定しますよね?つまり、APIがより一般的なものを期待している場合は、より具体的なものを渡すことができます(私のベースでは、「ISomeInterface」が一般的で、「SomeDerivedClass」が具体的です)。 「メソッドハンドラーのオーバーロードはDelegateTest.SomeClass.SomeEventDelegateに一致しません。」

なぜこれが機能しないのか疑問に思います。もしそうなら、どのような問題が発生するでしょうか?それとも私はそれが機能するために何かが欠けていますか?

前もって感謝します!

4

2 に答える 2

7

「in」は逆分散を指定しますよね?

はい。

つまり、APIがより一般的なものを期待している場合は、より具体的なものを渡すことができます(私のベースでは、「ISomeInterface」が一般的で、「SomeDerivedClass」が具体的です)。

いいえ。デリゲートの共変性により、デリゲートは、デリゲート型よりも派生が少ないパラメーター型のメソッドを参照できます。たとえばISomeInterface、ベースインターフェイスがあるとします。

interface ISomeBaseInterface
{
}

interface ISomeInterface : ISomeBaseInterface
{
}

そして、の代わりにhandler取ったとしましょう:ISomeBaseInterfaceSomeDerivedClass

static void handler(object sender, ISomeBaseInterface e) 

その後new SomeClass().SomeEvent += handler、動作します。

元のコードがタイプセーフではない理由は次のとおりですSomeClass。raisesの場合、引数として実装されるものSomeEventをすべて渡す可能性があります。たとえば、のインスタンスを渡すことができますが、のインスタンスを渡すこともできますISomeInterfacedataSomeDerivedClass

class SomeOtherDerivedClass : ISomeInterface
{
}

イベントに登録できvoid handler(object sender, SomeDerivedClass e)た場合、そのハンドラーはで呼び出されることになりますがSomeOtherDerivedClass、これは機能しません。

要約すると、より具体的なイベントハンドラーではなく、イベントタイプよりも一般的なイベントハンドラーを登録できます。

更新:あなたはコメントしました:

さて、私は実際にリストを繰り返し処理してタイプを確認したいと思います。したがって、SomeOtherDerivedObjectなどのタイプのデータオブジェクトでイベントが発生した場合、プログラムは、署名に一致するメソッド(object、SomeOtherDerivedObject)が見つかるまで、イベントにサブスクライブされているメソッドのリストを繰り返し処理します。したがって、イベント自体は、実際にデリゲートを呼び出すためではなく、保存するためにのみ使用されます。

eventC#では、任意のデリゲート型で機能するを宣言できるとは思いません。イベントハンドラーを追加して呼び出すメソッドを作成する方法は次のとおりです。

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

デリゲートの共変性に関する大きな問題の1つは、たとえばタイプのデリゲートが、をAction<Fruit>期待するルーチンに渡される可能性がある一方で、実際のタイプが「コンパイル時」タイプであっても、実際のタイプが失敗する2Action<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);
    }

デリゲートとデリゲートタイプが与えられると、このメソッドは、渡されたデリゲートのタイプが指定されたタイプと正確に一致するかどうかをチェックします。そうでない場合でも、元のデリゲートのメソッドに指定されたタイプの適切なシグネチャがある場合、必要な特性を備えた新しいデリゲートが作成されます。2つの別々の機会に、この関数が適切なタイプではないが互いに等しいと比較されるデリゲートを渡された場合、このメソッドによって返されるデリゲートも互いに等しく比較されることに注意してください。したがって、タイプのデリゲートを受け入れることになっているイベントがある場合、イベントに追加または削除する前に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 に答える