2010-06-15 15 views
8

Mam sytuacji gdzie mam klasyInterfejs do metody, która zwraca jego własny rodzaj

class Foo 
{ 
    Foo Bar() 
    { 
     return new Foo(); 
    } 
} 

Teraz I wan tot utworzyć interfejs bo

class IFoo 
{ 
    ??? Bar(); 
} 

Co powinno być w miejscu znaki zapytania? Każda klasa powinna zwracać swój własny typ, a nie Foo.

Poniższe rozwiązania działają, ale nie wyglądają na czyste. Nie rozumiem, dlaczego muszę podać tę samą klasę dwa razy, i nie ma to jak „to” dla bieżącego typu

ten sposób używam go później

class GenericClass<T> where T : IFoo 
{ 
    T foo = new T(); 
    T item = foo.Bar(); 
} 
+1

Problem z interfejsem generycznym polega na tym, że musisz teraz określić T wszędzie, gdzie używasz tego interfejsu w kodzie - więc połączyłeś interfejs z typem bazowym. Interfejsy zwykle próbują robić coś przeciwnego? –

+0

@Andrey - Tak jak powiedział Adam, jest to poważny zapach kodu. Moja odpowiedź zrobi to, o co prosisz, ale to nie jest dobra praktyka. – GenericTypeTea

+0

@GenericTypeTea, przepraszam, że powinienem był wyjaśnić, że to zadziała :) –

Odpowiedz

9

Pytasz:

Bel rozwiązania ow dzieło, ale nie wygląda na czyste. Nie rozumiem, dlaczego muszę określić tę samą klasę dwa razy, i nie ma to jak "to" dla bieżącego typu

Powodem, dla którego musisz określić to dwa razy, jest to, że C# nie ma funkcji, którą potrzeba. Co chcesz, jest coś takiego:

interface IFoo 
{ 
    IFoo Bar(); 
} 

class Foo : IFoo 
{ 
    Foo Bar() // should work since Foo is an IFoo, but it's not supported by C# 
    { 
     return new Foo(); 
    } 
} 

Z punktu widzenia bezpieczeństwa typu widzenia, to powinno działać (to się nazywa return type covariance). W rzeczywistości inne języki programowania, takie jak C++ lub Java, obsługują to, patrz this example on Wikipedia. Niestety, kowariancja zwrotna typu nie jest obsługiwana przez C# (nawet C# 4.0, która wprowadziła kowariancję dla generycznych), dlatego musisz użyć "obejścia generycznego" zilustrowanego w innych odpowiedziach.

kowariantna rodzaje powrotne a także "tego" typu proponowane są możliwości dla nowych wersjach C#:

+0

Twój kod się nie skompiluje, ponieważ klasa Foo nie implementuje 'IFoo Bar()'. Możesz zmienić Foo, aby Bar zwrócił wartość IFoo, ale będzie to oznaczać, że musisz upchnąć IFoo do Foo, gdziekolwiek chcesz uzyskać dostęp do metod Foo. (chociaż bezpieczne, niezbyt porządne do napisania).
Kowariancja i kontrawariancja są obsługiwane w typach ogólnych od C# 4.0, ale nie są tak naprawdę potrzebne w tym scenariuszu. Rozwiązanie zamieszczone w GenerticTypeTea jest wystarczające. –

+0

@ Mark H - Myślę, że to, co mówi Heinzi, brzmi: "to jest to, co OP chce ... ale to nie jest możliwe". – GenericTypeTea

+0

@GenericTypeTea: Dokładnie. :-) Właściwie, mówię "To nie jest możliwe w C# (ale w innych językach)." – Heinzi

8

można dodać typ rodzajowy i ograniczyć go za pomocą Typ interfejsu:

public interface IFoo<T> 
{ 
    T Bar(); 
} 

można by zaimplementować to w następujący sposób:

public class Foo : IFoo<Foo> 
{ 
    public Foo Bar() 
    { 
     return new Foo(); 
    } 
} 

public class Cheese : IFoo<Cheese> 
{ 
    public Cheese Bar() 
    { 
     return new Cheese(); 
    } 
} 

Aktualizacja, jeśli nie dbasz o betonowej typ zwracany Foo, a następnie można wykonać następujące czynności:

public interface IFoo 
{ 
    IFoo Bar(); 
} 

który jest realizowany następująco:

public class Foo : IFoo 
{ 
    public IFoo Bar() 
    { 
     return new Foo(); 
    } 
} 

Następnie w swojej generycznej klasy:

public class GenericClass<T> where T : class, IFoo, new() 
{ 
    public T Rar() 
    { 
     T foo = new T(); 
     T item = foo.Bar() as T; 
     return item; 
    } 
} 

GenericClass<Foo>.Rar(); będzie konkretną realizacją Foo.

+0

To nie zostanie skompilowane. – SLaks

+0

@SLaks: Po prostu musi dodać "publiczny", prawda? –

+0

Wygląda dobrze dla mnie. Dlaczego nie miałoby się go skompilować? – jalf

0
public interface IFoo<T> 
{ 
    T Bar(); 
} 

Twój realizacja byłaby wtedy:

class Foo : IFoo<Foo> 
{ 
    Foo Bar() 
    { 
     return new Foo(); 
    } 
} 

class Baz : IFoo<Baz> 
{ 
    Baz Bar() 
    { 
     return new Baz(); 
    } 
} 
0

Musisz zrobić interfejs rodzajowy, jak to:

interface IFoo<TClass> where TClass : IFoo<TClass>, class { 
    TClass Bar(); 
} 
2

Można użyć abstrakcyjną klasę bazową plusa wyraźny Wykonanie Państwa do osiągnąć to. Po pierwsze, zadeklarować swój interfejs tak:

interface IFoo 
{ 
    IFoo Bar(); 
} 

Następnie zadeklarować rodzajowe abstrakcyjne klasy, która implementuje IFoo w wyraźny sposób, a także oświadcza, abstrakcyjny sposób, że niby „przeciążenia” bar(), ale w ogólnym sposób:

abstract class BaseFooImpl<T> : IFoo where T : BaseFooImpl 
{ 
    public abstract T Bar(); 

    IFoo IFoo.Bar() 
    { 
     return Bar(); // this will call the abstract Bar() 
    } 
} 

teraz zdefiniować konkretne zajęcia tak:

class ConcreteFoo : BaseFooImpl<ConcreteFoo> 
{ 
    public override ConcreteFoo Bar() 
    { 
     return this; // for example, of course. 
    } 
} 

zaletą tego podejścia jest to, że zawsze można użyć nietypowe odniesienia IFoo do przechowywania konkretnych instancji.Jeśli twój interfejs rodzajowy, nie można, na przykład, te deklarują:

IFoo mammalInstance, fishInstance; // Instead of IFoo<Mammal> mammalInstance; IFoo<Fish> fishInstance; 
List<IFoo> manyInstances; // Instead of List<IFoo<IFoo>>, which doesn't even work AFAIK 
+0

Co uniemożliwia komuś wypowiedzieć klasę C2: BaseFooImpl ? Nic. To nie ogranicza baru do powrotu C2. –

+0

@Eric, to niepokojące. To nawet unieważnia moją odpowiedź, jeśli trzymamy się potrzeb PO. Sądzę, że ten problem jest wspólny dla innych rozwiązań zamieszczonych tutaj. Jednak "C2" (lub lepiej, jego autor) wie, co robi. – Humberto

+0

@Eric Czy istnieje jakiś sposób, aby ograniczyć coś, aby zwrócić swój własny typ za pomocą interfejsu? Nawet "ICloneable" ogranicza tylko koder, aby zwrócić 'object'. Mogę łatwo mieć 'Apple' zwróci' Pomarańczowy' – DevinB

0

Nie jestem pewien, co chce osiągnąć, ale można to zrobić w ten sposób:

interface IFoo<T> 
{ 
    T Bar(); 
} 



    class Foo:IFoo<Foo> 
    { 

     #region IFoo<Foo> Members 

     public Foo Bar() 
     { 
      return new Foo(); 
     } 

     #endregion 
    } 

albo jak to:

interface IFoo 
    { 
     IFoo Bar(); 
    } 

class Foo : IFoo 
    { 

     #region IFoo Members 

     public IFoo Bar() 
     { 
      return new Foo(); 
     } 

     #endregion 
    } 
4

myślę, że prawdziwe pytanie brzmi: dlaczego potrzebujesz typu pochodnego w interfejsie? Interfejs jest właśnie z tego powodu - abstrahując od konkretnych klas. Jeśli jest to po prostu dla wygody, więc nie trzeba oddawać do Foo po wywołaniu bar(), można zaimplementować interfejs wyraźnie:

interface IFoo 
{ 
    IFoo Bar(); 
} 

class Foo : IFoo 
{ 
    public Foo Bar() 
    { 
     return new Foo(); 
    } 

    IFoo IFoo.Bar() 
    { 
     return Bar(); 
    } 
} 

zadać sobie pytanie: dlaczego wprowadzić interfejs kiedy chcesz konkretny typ?

+0

+1 To jest dokładnie to, co myślałem. – juharr

+0

Aby podać prosty przykład: ten obiekt jest później używany do wiązania siatki. W tym momencie nie używam interfejsu, który obejmuje tylko crud, ale zależy od właściwości, które reprezentują kolumny – Andrey

Powiązane problemy