2016-01-29 17 views
14

IEnumerable<Animal>を実装している基本クラスから派生したクラスにIEnumerable<Turtle>を実装しようとしています。派生クラスのIEnumerableベースクラスでLINQメソッドを使用できません

のメソッドのうち、base.Cast<Turtle>()(または基本要素の任意のLINQメソッド)をコンパイルできないのはなぜですか?

basethisに置き換えることは、明らかにStackOverflowExceptionとなります。 StackOverflowExceptionを投げずにthis.OfType<Animal>().Cast<Turtle>().GetEnumerator();作品とbase.Cast<Turtle>().GetEnumerator();を交換し、いくつかの理由

public interface IAnimal {} 

public class Animal : IAnimal {} 

public class Turtle : Animal {} 

public class AnimalEnumerable : IEnumerable<Animal> { 
    List<Animal> Animals = new List<Animal>(); 
    IEnumerator<Animal> IEnumerable<Animal>.GetEnumerator() { 
     return Animals.GetEnumerator(); 
    } 

    IEnumerator IEnumerable.GetEnumerator() { 
     return Animals.GetEnumerator(); 
    } 
} 

public class TurtleEnumerable : AnimalEnumerable, IEnumerable<Turtle> { 
    IEnumerator<Turtle> IEnumerable<Turtle>.GetEnumerator() { 
     return base.Cast<Turtle>().GetEnumerator(); //FAILS WITH "CANNOT RESOLVE SYMBOL Cast" 
    } 
} 

、私は、なぜ分からない:ここでは

は、問題を再現するために、最小限のコードサンプルです。

+0

どうしたらいいですか?これらは異なるクラスです - TurtleEnumerableはどのようにもTurtleから継承しません。IEnumerable srandppl

+0

TurtleEnumerableはAnimalEnumerableクラスから継承しているため、基本要素にアクセスできる必要があります。 –

+1

似たようなもの:http:// stackoverflow。com/questions/27883427/why-cant-i-call-an-extension-method-from-the-extended-type拡張タイプ – romanoza

答えて

10

他の回答がある場合は、コードに多数の問題があります。Turtleはコンパイルに失敗したクラスからのいずれかの方法でbase.Cast<Turtle>()(またはベース要素上の任意のLINQメソッド)を呼び出しますなぜ

:私はあなたの特定の質問にお答えしたいですか?

セクション7.6.8を参照してください。

ベースアクセスは、現在のクラスまたは構造体の同様の名前のメンバーによって隠されている基本クラスのメンバーにアクセスするために使用されます。

基本クラスのメンバーにアクセスしていますか? NO。拡張メソッドは、基本クラスではなく、拡張メソッドを含む静的クラスのメンバーです。

ベースアクセスは、インスタンスコンストラクタ、インスタンスメソッド、またはインスタンスアクセサのブロックでのみ許可されます。

ここでは問題ありません。

base.Iがクラスまたは構造体で発生する場合、そのクラスまたは構造体の基本クラスのメンバーを示す必要があります。

再度、Cast<T>は基本クラスのメンバーではありません。

基本アクセスが仮想関数メンバ(メソッド、プロパティ、またはインデクサ)を参照する場合、実行時に呼び出す関数メンバ(§7.5.4)の決定が変更されます。

仮想物にはアクセスしていません。拡張メソッドは静的です。

呼び出される関数メンバは、関数メンバの最も派生した実装をBに対して(その非実行時の型では通常のように)ベースアクセス)。したがって、仮想関数メンバのオーバーライド内で、ベースアクセスを使用して、関数メンバの継承された実装を呼び出すことができます。

だから今、私たちは、データベースアクセスの目的が何であるかを参照してくださいでした基底クラスのメンバを呼び出すために仮想の現在の型でオーバーライドされたメンバー、またはに非仮想ディスパッチを有効にするには、現在のタイプnewメンバーによって隠されています。これはあなたがbaseを使用しようとしているものではないので、あなたは失敗することになります。このようにオフラベルのbaseの使用を中止してください。仮想メンバーへの非仮想ディスパッチを試みるとき、または非表示のメンバーにアクセスを試みるときにのみそれを使用します。

2

私はその質問にお答えします:

なぜbase.Cast()(またはベース 要素上の任意のLINQメソッド)クラスタートルからいずれかの方法ではコンパイルに失敗呼び出すのだろうか?

その例外の理由はCastであり、他のそのような方法は、拡張メソッドあります。拡張メソッドは静的です。

public static class Extensions 
{ 
    public static void Method2(this Base b) ... 
} 

public class Base 
{ 
    public void Method1() ... 
} 

public class Derived:Base 
{ 
    public void Test() 
    { 
     base.Method1(); 

     base.Method2(); // Does not contain a definition 
    } 
} 

をそして、あなたが知っているように拡張メソッドは、本当にいい糖衣構文です:

たとえば、のは、その時に見てみましょう。クラスには実際には追加されていませんが、コンパイラはそのように感じます。だから、コンパイラはその一つにコード行を変更します:あなたが1つを使用してコードを置き換える場合

Extensions.Method2(base); 

をコンパイラがより適切なエラーメッセージを与える:キーワードbase使用すると、このコンテキストでは有効ではありません。

としてはMSDNで述べている:

ベース・クラスへのアクセスのみコンストラクタ、インスタンス 方法、またはインスタンスのプロパティアクセサに許可されています。

+0

なぜ、拡張メソッドがbaseキーワードを使用して呼び出し可能にならないのですか?醜い 'this.OfType ().Cast ().GetEnumerator();')以外は、基本要素にアクセスするための回避策を知っていますか? –

+0

静的メソッドから 'base'を呼び出してイテレータと要素にアクセスすることはできません。 –

+0

@ErwinMayer申し訳ありませんが、私は急いでいました。私はそれをよりデリケートに説明しようとします。 –

5

エリックリッペルトはこれまで幾分意識的な意思決定であったとhereと述べています。最初に基本クラスの実装にアクセスできる場合に、拡張メソッドを使用することは決してありませんでした。

あなたがそれについて考えるなら、ここでこれを行う必要もありません。保護されたGetEnumeratorプロパティまたはメソッドを作成して使用してください。基本的なオブジェクトの向き。ここではlinqを拷問する必要はない。

編集: 私の以前の提案はうまくいかないと指摘されました。とにかくforeachを使用して多くの頭痛を引き起こすので、2つの異なるIEnumerableインターフェイスを実装しないことをお勧めします。

私はこの実装は、あなたが実際に何をしたいかもしれないと信じるようになった

public interface IAnimal { } 

public class Animal : IAnimal { } 

public class Turtle : Animal { } 

public class AnimalEnumerable : IEnumerable<Animal> 
{ 
    IEnumerator<Animal> IEnumerable<Animal>.GetEnumerator() 
    { 
     throw new NotImplementedException(); 
    } 


    IEnumerator IEnumerable.GetEnumerator() 
    { 
     throw new NotImplementedException(); 
    } 
} 

public class TurtleEnumerable : AnimalEnumerable 
{ 
} 

あなたはその後、Animal及びその誘導体

+0

'IEnumerator 'を 'IEnumerator 'にキャストできないので、このコードはコンパイルされません。 –

+0

派生クラスで使用するIEnumerableを返す保護されたメソッドを基本クラスに作成する必要があります。 –

+0

'Turtle'クラスから' this.Animals.Cast ().GetIterator() 'を呼び出せるように、' List Animals'を保護された方法で公開しました。 –

1

は、なぜあなたは上のIEnumerableを実装しているすべてのようなを列挙することができますTurtleEnumeratorクラス?また、IEnumerableインターフェイスを実装したときのAnimalEnumerableのアクセシビリティは正しいとは思わない。

public interface IAnimal { } 

    public class Animal : IAnimal { } 

    public class Turtle : Animal { } 

    public class AnimalEnumerable : IEnumerable<Animal> 
    { 
     protected List<Animal> Animals = new List<Animal>(); 

     public IEnumerator<Animal> GetEnumerator() 
     { 
      return Animals.GetEnumerator(); 
     } 

     IEnumerator IEnumerable.GetEnumerator() 
     { 
      return Animals.GetEnumerator(); 
     } 
    } 

    public class TurtleEnumerable : AnimalEnumerable 
    { 
     public void AddTurtle(Turtle turtle) 
     { 
      Animals.Add(turtle); 
     } 

     public IEnumerable<Turtle> GetTurtles() 
     { 
      var iterator = GetEnumerator(); 

      yield return iterator.Current as Turtle; 
     } 
    } 

    [Test] 
    public void CanAddTurtles() 
    { 
     Turtle one = new Turtle(); 
     Turtle two = new Turtle(); 

     TurtleEnumerable turtleStore = new TurtleEnumerable(); 

     turtleStore.AddTurtle(one); 
     turtleStore.AddTurtle(two); 

     foreach (var turtle in turtleStore.GetTurtles()) 
     { 
      // Do something with the turtles.... 
     } 
    } 
+0

私は両方のIEnumerableを実装する2つのインターフェイスを実装する必要がある理由があります。私が使った例は単純化したものにすぎません。 しかし、私は 'Turtle'クラスから' this.Animals.Cast ().GetIterator() 'を呼び出せるように' List Animals'を保護された方法で公開しました。 –

2

あなたのアプローチにはいくつかの問題があります。

が、それはこのようなものを実装することはないでしょう。 TurtleEnumerableIEnumerable<Animal>IEnumerable<Turtle>の両方を実装しています。 foreachループでTurtleEnumerableインスタンスを使用することができるようにするには、コンパイルするコードのためにそれをキャストする必要があります:あなたはまた、一般的なGetEnumerator()方法を非表示にするには、明示的なインターフェイスの実装を使用している

foreach (var turtle in (IEnumerable<Turtle>) turtleEnumerable) 

。戻り値の型だけではオーバーロード解決を行うことができず、戻り値の型によって2つの汎用のGetEnumerator()メソッドが異なるため、これを行う必要があります。

しかし、TurtleEnumerableメソッドでは、GetEnumerator()メソッドを呼び出すことができません。この理由は、baseが "base"タイプの変数のように動作しないためです。代わりに、基本クラスのメソッドを呼び出すためにのみ使用できる予約語です。これには、baseでは拡張メソッドを使用できないということがあります。また、baseをキャストすることはできないので、基本クラスの明示的なインターフェイス実装はbaseによって呼び出し可能になりません。

しかし、いくつかの点でTurtleEnumerable.GetEnumerator()意志の実装を、あなたはthisをキャストすることができますが、TurtleEnumerable上の一般的なGetEnumerator()AnimalEnumerable上の汎用GetEnumerator()を隠しているため、あなたは、スタックオーバーフローを取得するように、基本クラスを呼び出すことができなくなります同じGetEnumeratorと呼んでください。あなたのコードを作るために

はあなたのベースクラスでprotected IEnumerator<Animal> GetEnumerator()メソッドを作成し、保護されたメソッドを呼び出すことによって取得することができますベースの列挙のインスタンスをラップ独自のTurtleEnumeratorクラスを作成する必要がありますコンパイルします。すべての実装IEnumerable<Base>IEnumerable<Derived>の両方が多くの問題にあなたを取得するコレクションを持つことに

public class TurtleEnumerable : AnimalEnumerable, IEnumerable<Turtle> { 

    IEnumerator<Turtle> IEnumerable<Turtle>.GetEnumerator() { 
    return new TurtleEnumerator(base.GetEnumerator()); 
    } 

    sealed class TurtleEnumerator : IEnumerator<Turtle> { 

    IEnumerator<Animal> animalEnumerator; 

    public TurtleEnumerator(IEnumerator<Animal> animalEnumerator) { 
     this.animalEnumerator = animalEnumerator; 
    } 

    public Turtle Current { 
     get { return (Turtle) animalEnumerator.Current; } 
    } 

    Object IEnumerator.Current { 
     get { return Current; } 
    } 

    public Boolean MoveNext() { 
     return animalEnumerator.MoveNext(); 
    } 

    public void Reset() { 
     animalEnumerator.Reset(); 
    } 

    public void Dispose() { 
     animalEnumerator.Dispose(); 
    } 

    } 

} 

すべて。あなたはこのデザインを使って何を達成しようとしていますか?ジェネリックList<T>を使用して

、あなたはこのようなことを行うことができますcontravariance:

IEnumerable<Turtle> turtles = new List<Turtle>(); 
IEnumerable<Animal> animals = (IEnumerable<Animal>) turtles; 

ことが必要な場合にも、独自のジェネリックコレクション型でList<T>を置き換えることができます。

関連する問題