36

C# 4.0 の共変性と反変性のサポートによる奇妙な動作:

using System;

class Program {
  static void Foo(object x) { }
  static void Main() {
    Action<string> action = _ => { };

    // C# 3.5 supports static co- and contravariant method groups
    // conversions to delegates types, so this is perfectly legal:
    action += Foo;

    // since C# 4.0 much better supports co- and contravariance
    // for interfaces and delegates, this is should be legal too:
    action += new Action<object>(Foo);
  }
}

での結果ですArgumentException: Delegates must be of the same type.

奇妙ですね。Delegate.Combine()(デリゲートで操作を実行するときに呼び出される+=) が実行時に共変性と反変性をサポートしないのはなぜですか?

さらに、BCL のデリゲート型には、そのジェネリックパラメーターSystem.EventHandler<TEventArgs>に反変の注釈がないことがわかりました。TEventArgsなんで?これは完全に合法で、TEventArgs入力位置でのみ使用される型です。Delegate.Combine()?でバグをうまく隠しているため、反変の注釈がない可能性があります。;)

ps これはすべて、VS2010 RC 以降のバージョンに影響します。

4

4 に答える 4

38

簡単に言うと、デリゲートの結合は、分散に関してすべて台無しです。これはサイクルの後半に発見されました。私たちは CLR チームと協力して、下位互換性を損なうことなくすべての一般的なシナリオを機能させる方法などを考え出すことができるかどうかを検討していますが、私たちが思いついたものはおそらく 4.0 リリースにはなりません。うまくいけば、いくつかのサービスパックですべてが整理されるでしょう. ご迷惑をおかけし申し訳ございません。

于 2010-02-21T23:38:56.003 に答える
6

共変性と反変性は、ジェネリック型間の継承関係を指定します。共分散と反分散がある場合、クラスG<A>とは何とG<B>であるかによって何らかの継承関係にある可能性があります。これは、ジェネリック メソッドを呼び出すときに役立ちます。AB

ただし、Delegate.Combineメソッドは一般的ではなく、ドキュメントには例外がいつスローされるかが明確に記載されています。

ArgumentException- abはどちらも参照ではなくnull( NothingVisual Basic では)、abは同じデリゲート型のインスタンスではありません。

現在、Action<object>Action<string>は確かに異なるデリゲート型のインスタンスであるため (継承関係を介して関連付けられていても)、ドキュメントによると、例外がスローされます。メソッドがこのシナリオをサポートできることは合理的にDelegate.Combine思えますが、それは単なる可能な提案です (継承されたデリゲートを宣言できないため、これまでは明らかにこれは必要ありませんでした。したがって、co/contra-variance の前は、デリゲートは継承関係を持っていませんでした)。

于 2010-02-21T19:30:04.473 に答える
1

デリゲートの組み合わせに関する問題の 1 つは、どのオペランドがサブタイプで、どのオペランドがスーパータイプであるかを指定しない限り、結果がどのタイプになるべきかが明確でないことです。指定された数の引数と byval/byref のパターンを持つ任意のデリゲートをスーパータイプに変換するラッパー ファクトリを作成することは可能ですが、同じデリゲートでそのようなファクトリを複数回呼び出すと、異なるラッパーが生成されます (これは、イベントのサブスクリプション解除)。別の方法として、右側のデリゲートを左側のデリゲートの型に強制するバージョンの Delegate.Combine を作成することもできますが (おまけとして、戻り値を型キャストする必要はありません)、特別なバージョンのデリゲートを作成する必要があります。 .remove で対処します。

于 2010-12-23T18:12:15.650 に答える
0

このソリューションは、最初に私の質問に対して cdhowie によって投稿されました:デリゲートの変換は平等を破り、イベントから切断できませんが、マルチキャスト デリゲートのコンテキストで共分散と反分散の問題を解決するようです。

最初にヘルパー メソッドが必要です。

public static class DelegateExtensions
{
    public static Delegate ConvertTo(this Delegate self, Type type)
    {
        if (type == null) { throw new ArgumentNullException("type"); }
        if (self == null) { return null; }

        if (self.GetType() == type)
            return self;

        return Delegate.Combine(
            self.GetInvocationList()
                .Select(i => Delegate.CreateDelegate(type, i.Target, i.Method))
                .ToArray());
    }

    public static T ConvertTo<T>(this Delegate self)
    {
        return (T)(object)self.ConvertTo(typeof(T));
    }
}

デリゲートがある場合:

public delegate MyEventHandler<in T>(T arg);

デリゲートを目的の型に変換するだけで、デリゲートを組み合わせるときに使用できます。

MyEventHandler<MyClass> handler = null;
handler += new MyEventHandler<MyClass>(c => Console.WriteLine(c)).ConvertTo<MyEventHandler<MyClass>>();
handler += new MyEventHandler<object>(c => Console.WriteLine(c)).ConvertTo<MyEventHandler<MyClass>>();

handler(new MyClass());

ConvertTo()メソッドを使用して、同様にイベントからの切断もサポートします。デリゲートのカスタム リストを使用する場合とは異なり、このソリューションはすぐに使用できるスレッド セーフを提供します。

ここで見つけることができるいくつかのサンプルを含む完全なコード: http://ideone.com/O6YcdI

于 2015-05-05T11:56:19.307 に答える