2011-08-12 15 views
9

Wiem, że członkowie shadowingu w implementacjach klas mogą prowadzić do sytuacji, w których "niewłaściwy" członek może zostać wywołany w zależności od tego, w jaki sposób rzuciłem moje instancje, ale z interfejsami nie widzę, że może to stanowić problem i sam się znajduję pisanie interfejsy jak to dość często:Shadowing Inherited Generic Interface Członkowie w .NET: dobry, zły czy brzydki?

public interface INode 
{ 
    IEnumerable<INode> Children { get; } 
} 

public interface INode<N> : INode 
    where N : INode<N> 
{ 
    new IEnumerable<N> Children { get; } 
} 

public interface IAlpha : INode<IAlpha> 
{ } 

public interface IBeta : INode<IBeta> 
{ } 

mam miejsca w kodzie, które znają tylko o INode więc dzieci powinny być również typu INode.

W innych miejscach, chcę wiedzieć o konkretnych typów - w realizacji mojego przykładu IAlpha & IBeta interfejsów chcę dzieci mają być wpisane tak samo jak ich rodziców.

więc zaimplementować klasę NodeBase tak:

public abstract class NodeBase<N> : INode<N> 
    where N : INode<N> 
{ 
    protected readonly List<N> _children = new List<N>(); 

    public IEnumerable<N> Children 
    { 
     get { return _children.AsEnumerable(); } 
    } 

    IEnumerable<INode> INode.Children 
    { 
     get { return this.Children.Cast<INode>(); } 
    } 
} 

Nie shadowing w rzeczywistej implementacji, tylko w interfejsach.

szczególne przypadki IAlpha & IBeta wyglądać następująco:

public class Alpha : NodeBase<Alpha>, IAlpha 
{ 
    IEnumerable<IAlpha> INode<IAlpha>.Children 
    { 
     get { return this.Children.Cast<IAlpha>(); } 
    } 
} 

public class Beta : NodeBase<Beta>, IBeta 
{ 
    IEnumerable<IBeta> INode<IBeta>.Children 
    { 
     get { return this.Children.Cast<IBeta>(); } 
    } 
} 

Znowu nie cieniowanie w realizacji.

mogę teraz dostęp do tych rodzajów tak:

var alpha = new Alpha(); 
var beta = new Beta(); 

var alphaAsIAlpha = alpha as IAlpha; 
var betaAsIBeta = beta as IBeta; 

var alphaAsINode = alpha as INode; 
var betaAsINode = beta as INode; 

var alphaAsINodeAlpha = alpha as INode<Alpha>; 
var betaAsINodeBeta = beta as INode<Beta>; 

var alphaAsINodeIAlpha = alpha as INode<IAlpha>; 
var betaAsINodeIBeta = beta as INode<IBeta>; 

var alphaAsNodeBaseAlpha = alpha as NodeBase<Alpha>; 
var betaAsNodeBaseBeta = beta as NodeBase<Beta>; 

każdej z tych zmiennych mają teraz poprawne, silnie typu Children kolekcję.

Moje pytania są proste. Czy cieniowanie członków interfejsu przy użyciu tego rodzaju wzoru jest dobre, złe czy brzydkie? I dlaczego?

+0

Dlaczego po prostu nie używać wpisz argument, aby określić typ dziecka. Wtedy INODA nadal miałaby te same cechy, ale nie musiałaby w ogóle cienia –

+0

@Rune - Co rozumiesz przez "argument typu"? – Enigmativity

+0

@Enigmativity: Edytowałem swoją odpowiedź, aby uprościć implementację INode.Children przez NodeBase - opracowałem, czego wcześniej brakowało. –

Odpowiedz

8

Powiedziałbym, że masz sobie dość skomplikowany scenariusz istnieje, a ja generalnie spróbować zachować rzeczy prostsze niż to - ale czy to działa dla Ciebie, myślę, że to jest w porządku, aby dodać więcej informacji jak to. (Wydaje się rozsądne, aż dojdziesz do bitu IAlpha i IBeta; bez tych interfejsów, Alpha i Beta nie potrzeba żadnego wdrożenia w ogóle, a dzwoniący może po prostu użyć INode<IAlpha> i INode<IBeta> zamiast

W szczególności należy zauważyć, że IEnumerable<T>. skutecznie robi to samo - nie ukrywa jeden rodzajowy z innym, co prawda, ale ukrywanie nierodzajową z ogólnym

Cztery inne punkty.

  • wezwanie do AsEnumerable w NodeBase jest bez sensu; abonenci dzwoniący mogą nadal przesyłać do List<T>. Jeśli chcesz temu zapobiec, możesz zrobić coś takiego, jak Select(x => x). (W teorii Skip(0)potęga pracy, ale mógłby być zoptymalizowane z dala; LINQ to Objects nie jest strasznie dobrze udokumentowane zgodnie z którym operatorzy są gwarantowane, aby ukryć oryginalną realizację Select gwarantuje nie realistycznie, Take(int.MaxValue) będzie.. też działa.)

  • Od C# 4, twoi "liść" klas dwa mogą zostać uproszczone ze względu na kowariancji:

    public class Alpha : NodeBase<Alpha>, IAlpha 
    { 
        IEnumerable<IAlpha> INode<IAlpha>.Children { get { return Children; } } 
    } 
    
    public class Beta : NodeBase<Beta>, IBeta 
    { 
        IEnumerable<IBeta> INode<IBeta>.Children { get { return Children; } } 
    } 
    
  • Od C# 4, implementacja NodeBase z INode.Children można uprościć jeśli you” ponownie gotów ograniczyć N być typ referencyjny:

    public abstract class NodeBase<N> : INode<N> 
        where N : class, INode<N> // Note the class constraint 
    { 
        ... 
    
        IEnumerable<INode> INode.Children 
        { 
         get { return this.Children; } 
        } 
    } 
    
  • od C# 4, można zadeklarować INode<N> do być kowariantna w N:

    public interface INode<out N> : INode 
    
+0

Dzięki za kolejną doskonałą odpowiedź. Zwrócił mi uwagę na temat, że 'AsEnumerable' można odrzucić z powrotem do' Listy '. W Rx metoda 'AsObservable' ** ** ukrywa źródło obserwowalne. Po prostu założyłem, że tak samo było z "AsEnumerable". Może faceci Rx są po prostu sprytni. – Enigmativity

0

Dlaczego nie wystarczy użyć argumentu typu (to jest argument typu rodzajowego), aby określić typ dziecka. Wtedy INOD nadal będzie miał te same elementy, ale nie będziesz musiał cienia w ogóle I rzeczywiście masz shadowing w rzutowaniu implementacji do INode spowoduje to te same problemy, które opisałeś w swoim poście

Powiązane problemy