9

次のスニペットでエラーが発生しない理由を理解するのに問題があります

public void SomeMethod<T>(T arg) where T : MyInterface
{
  MyInterface e = arg;
}

しかし、これはジェネリック型の制約のために機能すると予想されます

private readonly IList<Action<MyInterface>> myActionList = new List<Action<MyInterface>>();

public IDisposable Subscribe<T>(Action<T> callback) where T: MyInterface
{
  myActionList.Add(callback); // doesn't compile
  return null
}

このエラーが発生します

cannot convert from 'System.Action<T>' to 'System.Action<MyInterface>'

VS2012sp1と.NET4.5を使用しています。

制約がこれをコンパイルできない理由を誰かが説明できますか?

4

6 に答える 6

5

これは反変性の問題です-引数としてAction<MyInterface>任意のインスタンスをとることができるはずですが、安全ではないのサブタイプであるwhereMyInterfaceを格納しようとしています。Action<T>TMyInterface

たとえば、次の場合:

public class SomeImpl : MyInterface { }
public class SomeOtherImpl : MyInterface { }
List<Action<MyInterface>> list;

list.Add(new Action<SomeImpl>(i => { }));
ActionMyInterface act = list[0];
act(new SomeOtherImpl());

タイプがタイプよりも「小さい」場合にのみ、Action<T>一部にを割り当てることができます。例えばAction<U>TU

Action<string> act = new Action<object>(o => { });

文字列引数はオブジェクト引数がある場合は常に有効であるため、安全です。

于 2013-03-25T09:12:06.913 に答える
3

クラスとデリゲートは同じものではありません。System.Action<MyInterface>タイプの単一のパラメーターを持つ関数を表し、タイプのMyInterfaceパラメーターSystem.Action<T>を持つメソッドを表しますT : MyInterface。関数のシグニチャには互換性がありませんT。の派生物である関連性はありません。シグニチャは、が正確にMyInterfaceである場合にのみ互換性があります。TMyInterface

于 2013-03-25T09:12:55.080 に答える
2

where T: MyInterface制約とは、「MyInterfaceを実装する任意のクラスまたは構造体の任意のインスタンス」を意味ます

したがって、あなたがやろうとしていることは、次のように単純化することができます。

Action<IList> listAction = null;
Action<IEnumerable> enumAction = listAction;

まだ動作するはずではありませんがIList : IEnumerable。詳細については、こちらをご覧ください。

http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx http://msdn.microsoft.com/en-us/library/dd799517.aspx

したがって、インターフェイスだけでなく汎用を本当に使用する必要がある場合は、このように行うことができますが、複雑さとマイナーなパフォーマンスの問題が追加されます。

public static IDisposable Subscribe<T>(Action<T> callback) where T : MyInterface
{
    myActionList.Add(t => callback((T)t)); // this compiles and work
    return null;
}
于 2013-03-25T09:19:29.887 に答える
2

このような状況では、行動を許可した場合に何がうまくいかないかを検討することが役立つと思います。それで、それを考えてみましょう。

interface IAnimal { void Eat(); }
class Tiger : IAnimal 
{ 
  public void Eat() { ... }
  public void Pounce() { ... } 
}
class Giraffe : IAnimal 
...
public void Subscribe<T>(Action<T> callback) where T: IAnimal
{
   Action<IAnimal> myAction = callback; // doesn't compile but pretend it does.
   myAction(new Giraffe()); // Obviously legal; Giraffe implements IAnimal
}
...
Subscribe<Tiger>((Tiger t)=>{ t.Pounce(); });

では、どうなりますか?トラを連れて飛び跳ねるデリゲートを作成し、それをに渡し、それをSubscribe<Tiger>に変換しAction<IAnimal>、キリンを渡します。その後、跳ねます。

明らかに、それは違法でなければなりません。違法にすることが賢明な唯一の場所は、からAction<Tiger>への変換Action<IAnimal>です。だからそれは違法です。

于 2013-03-25T14:52:22.453 に答える
1

クラスとデリゲートの動作は少し異なります。簡単な例を見てみましょう。

public void SomeMethod<T>(T arg) where T : MyInterface
{
  MyInterface e = arg;
}

このメソッドでは、Tが少なくともであると想定できるため、argsは常ににキャストできるためMyInterface、このようなことを行うことができます。MyInterface e = arg;MyInterface

次に、デリゲートがどのように動作するかを見てみましょう。

public class BaseClass { };
public class DerivedClass : BaseClass { };
private readonly IList<Action<BaseClass >> myActionList = new List<Action<BaseClass>>();

public void Subscribe<T>(Action<T> callback) where T: BaseClass
{
  myActionList.Add(callback); // so you could add more 'derived' callback here Action<DerivedClass>
  return null;
}

ここで、DerivedClassコールバックをmyActionListに追加してから、デリゲートを呼び出す場所を追加します。

foreach( var action in myActionList ) {
   action(new BaseClass);
}

ただし、DerivedClassコールバックがある場合は、パラメーターとしてDerivedClassを渡す必要があるため、これを行うことはできません。

この質問は、共変性と反変性について言及しています。この記事から分散について読むことができます。また、Eric Lippertには分散に関する非常に興味深い記事があります。これは最初の記事であり、残りは彼のブログで見つけることができます。

PSリーのコメントに従って編集。

于 2013-03-25T09:39:32.973 に答える
0

とにかく特定のインターフェースに制限されている場合Tは、代わりにそのインターフェースを使用できます。

public void SomeMethod(MyInterface arg)
{
  MyInterface e = arg;
}

private readonly IList<Action<MyInterface>> myActionList = new IList<Action<MyInterface>>();

public IDisposable Subscribe(Action<MyInterface> callback)
{
  myActionList.Add(callback); // does compile
  return null
}

動作してコンパイルされ、現在の状態と実質的に同じです。

ジェネリックスは、タイプに関係なく同じ操作を実行する場合に役立ちます。その後、ジェネリックスの目的を破ったインターフェイスにタイプを制限すると、代わりにそのインターフェイスを使用する必要があります。

于 2013-03-25T09:10:21.987 に答える