2013-05-29 15 views
5

Biorąc pod uwagę (bardzo uproszczony) kod.Brak metody rozszerzenia "Pierwszy" w klasie pochodnej

public class Class1 
{ 
} 

public class Class2 : Class1 
{ 
} 

public class List1 : System.Collections.Generic.IEnumerable<Class1> 
{ 
    public new System.Collections.Generic.IEnumerator<Class1> GetEnumerator() 
    { 
     yield return new Class1();    
    } 

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


public class List2 : List1 , System.Collections.Generic.IEnumerable<Class2> 
{  
    public new System.Collections.Generic.IEnumerator<Class2> GetEnumerator() 
    { 
     yield return new Class2();    
    } 

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

Następnie kod

var l = new List2(); 
var first = l.First(); 

nie skompilować, ale daje błąd

'lista2' nie zawiera definicji 'First', a nie metoda rozszerzenie 'pierwsze' akceptowanie pierwszego argumentu typu "List2" można znaleźć (czy brakuje instrukcji użycia lub odniesienia do zespołu?)

Jeśli Lista2 nie jest wyprowadzona z Listy1, kompiluje się poprawnie, co dowodzi, że List2 ma poprawną metodę rozszerzenia.

Czy to po prostu przypadek błędnego błędu, a problem polega na tym, że ma dwie metody rozszerzenia i nie wie, który wybrać?

Jeśli tak, dlaczego nie można stwierdzić, że klasa 2 jest bardziej szczegółową wersją w taki sam sposób, w jaki używa kompilatora z metodą zabezpieczenia przed przeciążeniem?

+3

Oczywiście masz 'using System.Linq;', prawda? –

+0

Tak. (W przeciwnym razie nie skompiluje się, gdy List2 nie pochodzi z Listy1). – sgmoore

Odpowiedz

7

Problemem jest to, że List2 realizuje zarówno IEnumerable<Class1> i IEnumerable<Class2>, więc kompilator nie wie, który z Enumerable.First<Class1> lub Enumerable.First<Class2> zadzwonić. W rzeczywistości nie ma najlepszego T, który kompilator może znaleźć, aby wywołać Enumerable.First<T>(l), a więc nie liczy żadnego generycznego Enumerable.First<T> jako odpowiedniej metody. Dlatego obecnie szuka tylko nietypowej metody o nazwie First z pojedynczym parametrem, do której można niejawnie przekonwertować l i której nie może znaleźć.

Możesz być jawne i powiedzieć

var first = l.First<Class1>(); 

lub

var first = l.First<Class2>(); 

i wtedy jesteś w porządku.

+0

Jaka jest różnica między tymi dwoma połączeniami i czego oczekujesz od metody first.GetType() w obu przypadkach? – sgmoore

2

Rozważmy tę klasę: Metoda

public class List1 : IEnumerable<string>, IEnumerable<object> 
     { 
      IEnumerator<object> IEnumerable<object>.GetEnumerator() 
      { 
       return GetEnumerator(); 
      } 

      public IEnumerator<string> GetEnumerator() 
      { 
       throw new NotImplementedException(); 
      } 

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

więc kompilator nie wie Pierwsze(), które z IEnumerable<string> lub IEnumerable<object> zadzwonić. ten przykład to uproszczona wersja twojej sytuacji.

Należy pamiętać, że napisano metodę rozszerzenia dla klasy, która nie ma kodu źródłowego, ale w następnej wersji implementuje dokładnie tę samą metodę dla tej klasy. W tej sytuacji twoje stare kody nie są już wiarygodne, więc najlepszym rozwiązaniem jest błąd czasu kompilacji.

+0

Dobre wyjaśnienie. – mehrandvd

-1

Na czym polega problem z metodami ogólnymi?

Gdy klasa dziedziczy z List1 iz IEnumerable<Class2> to faktycznie dziedziczy z 3 typów: List1, IEnumerable<Class1> i IEnumerable<Class2> (i object). Oznacza to, że twoja klasa ma 2 różne implementacje dla First<>() z tego powodu, że kompilator nie wygeneruje "domyślnej" metody, która nie bierze żadnych ogólnych argumentów, ponieważ nie może dowiedzieć się, co to jest.

Nie oznacza to, że First<>() nie jest dostępny, to tylko oznacza, że ​​trzeba określić, co jest T.

Rozważmy następujący fragment kodu:

 var l2 = new List2(); 

     l2.First<Class1>(); 
     l2.First<Class2>(); 

Zarówno First<Class1>() i First<Class2>() są dostępne i od tamtej pory kompilator nie może dowiedzieć się, co jest T.

Ale w tym fragmencie kodu:

l2.First((Class2 c) => c.ToString() == ""); 

Kompilator może zrozumieć, że T jest Class2 tak dalej będzie skompilować dobrze.

Dobre praktyki projektowania

Chociaż poprzednie podejście jest ważne, nie jest to dobra praktyka projektowania mieć klasę, która dziedziczy ten sam interfejs dwukrotnie. (w twoim przypadku, raz przez Class1, a drugi przez jawne dziedziczenie IEnumerable<Class2>). Aby uzyskać lepszy projekt, możesz zaimplementować wspólną klasę abstrakcyjną i utworzyć listy, które będą pochodzić z tej klasy.

public abstract class CommonListBase 
{ 

} 

public class List1 : CommonListBase, IEnumerable<Class1> 
{ 
    public System.Collections.Generic.IEnumerator<Class1> GetEnumerator() 
    { 
     yield return new Class1(); 
    } 

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


public class List2 : CommonListBase, IEnumerable<Class2> 
{ 
    public System.Collections.Generic.IEnumerator<Class2> GetEnumerator() 
    { 
     yield return new Class2(); 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return this.GetEnumerator(); 
    } 
} 
Powiązane problemy