2013-03-25 10 views
8

私は理解し、いくつかの問題を抱えています次のスニペットは、私にエラー一般的な制約が期待どおりに動作しません

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 
} 

は、私はVS2012 SP1と.NET 4.5を使用しています。このエラーに

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

を与えます。

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

+1

なぜあなたのリストは読み込み専用ですか、なぜ新しいIListですか?それは実際の宣言ですか? –

+1

クラスとデリゲートは同じではありません。 'System.Action 'は 'MyInterface '型の単一パラメータを持つ関数を表し、' System.Action 'は' T:MyInterface'型のパラメータを持つメソッドを表します。関数のシグネチャは互換性がなく、 'T'が' MyInterface'の派生ではないということはなく、 'T'が正確に' MyInterface'だった場合にのみ署名が互換性があります。 –

+0

@PaoloTedesco申し訳ありません、それは再構築され、他のコードから簡素化されています。コピー/貼り付けのエラー –

答えて

3

クラスとデリゲートは同じではありません。 System.Action<MyInterface>は、タイプがMyInterfaceの単一のパラメータを持つ関数を表し、System.Action<T>は、タイプがT : MyInterfaceのメソッドを表す関数を表します。関数の署名は互換性がなく、の派生語はMyInterfaceではなく、Tが正確にMyInterfaceの場合にのみ署名が互換性があります。

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 
} 

が動作し、コンパイルして、実質的にあなたが今持っているものと同じであるだろう。

ジェネリックはジェネリックの目的を破ったインターフェースにタイプを限定し、代わりにそのインターフェースを使用するだけでよい場合は、タイプにかかわらず同じ操作をしたい場合に便利です。しかし、あなたはTは安全ではありませんMyInterfaceのいくつかのサブタイプで、あるAction<T>を保存しようとしている、Action<MyInterface>は、引数として任意のMyInterfaceインスタンスを取ることができるはず -

+0

私は彼がその特定のインターフェイスのサブタイプを渡すことができると思う。 MyOtherInterface:MyInterface –

+0

@Rogerそれは本当かもしれない、私はOPからそれを得ていないが、私はそれも私が考慮しなかったアプローチだと認めます。 – Bazzz

4

これはcontravarianceの問題です。例えば

あなたが持っていた場合:タイプTがタイプUより「小さい」の場合

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<U>Action<T>を割り当てることができます。たとえば、オブジェクト引数が常に文字列引数が有効なので、たとえば

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

は安全です。

+0

例の説明のため+1。 – nawfal

1

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

だから何あなたがやろうとしているが、このように単純化することができます。まだIList : IEnumerableながら、動作するようになっていません

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

。詳細はここで見つけることができます:

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; 
} 
1

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

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

あなたは引数が常にMyInterfaceにキャストする可能性があるため、あなたがこのMyInterface e = arg;ような何かを行うことができますので、Tは、少なくともMyInterfaceだろうと想定できます。今

のは、代表者がどのように動作するかを見てみましょう:ので、

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

しかし、あなたはそれを行うことはできません。myActionListへ

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

今we'rが追加DerivedClassコールバックして、どこかにあなたがデリゲートを呼び出しますDerivedClassコールバックがあれば、パラメータとしてDerivedClassを渡す必要があります。

この質問はCovariance and contravarianceを参照しています。あなたはthisの記事から分散について読むことができます。また、Eric Lippertは分散に関する記事を非常に織り込んでいます。thisが最初の記事です。彼のブログで残りを見つけることができます。

P.S.リーコメントへの編集。

+0

クラスは共変できません。デリゲートとインターフェイスのみが分散注釈を持つことができます。また、代理人が反動を持っていると言うのは間違いです.Func'のデリゲート型は、引数に反反的であり、戻り型で共変です。 Contravarianceはデリゲート型に限定されません。たとえば 'IObserver 'インタフェースを参照してください。これは 'T'では反反です。 – Lee

+0

投稿を編集しました。ありがとうございます。 – Andrew

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>への変換です。だから違法です。

関連する問題