2016-08-01 19 views
5

Próbuję utworzyć wspólny interfejs, który pozwoli mi korzystać z metod interakcji z bazą danych. Chcę, aby moja aplikacja biznesowa była w stanie utworzyć instancję dowolnej metodologii połączeń i mieć pewność, że interfejs jest identyczny.Implementowanie ogólnych metod z interfejsu przy użyciu innego interfejsu

Oto uproszczona wersja tego, co próbuję teraz.

Interfejs bazy danych, w którym IElement to kolejny interfejs definiujący tabelę. Interfejs

public interface IDatabase 
{ 
    void setItem(IElement task); //this works fine 
    List<T> listTasks<T>() where T : IElement; // this doesn't 
} 

IElement:

public interface IElement 
{ 
    int id { get; set; } 
} 

Realizacja IElement:

public class TaskElement: IElement 
{ 
    public int id { get; set; } 
    public string name {get; set; } 
} 

Realizacja IDatabase:

public class SQLiteDb: IDatabase 
{ 
    public SqLiteDb(SQLiteConnection conn) 
    { 
     database = conn; 
    } 

    public void setItem(IElement task) 
    { 
     // works fine when passed a new TaskElement() which is an implementation of IElement. 
     database.Insert(task); 
    } 

    //it all goes off the rails here 
    public List<T> listItems<T>() where T : IElement 
    { 
     var returnList = new List<IElement> 

     foreach (var s in database.Table<TaskElement>()) 
     { returnList.Add(s); } 

     return returnList; 
    } 

Próbowałem wiele wariacji na ten temat, ale każdy daje mi nową pozwać. Tutaj na przykład są dwa błędy.

1)

typu argumenty dla metody 'SQLiteDb.listTasks<T>()' nie można wywieść ze stosowania. Spróbuj jawnie określić argumenty typu.

2)

nie można niejawnie przekonwertować typu 'System.Collections.Generic.List<TaskElement>' do 'System.Collections.Generic.List<T>'

Próbowałem zmiany sposobu korzystania z wyraźną typ ale mieli tam problemy. Jeśli używam IElement (mój ogólny interfejs dla wszystkich elementów), nie mogę zwrócić listy obiektów TaskElement (moja implementacja IElement), ponieważ nie pasuje ona do typu zwracanego (List<IElement>), a jeśli zmienię typ zwracany na List<TaskElement> I "Nie będę już wdrażać interfejsu.

Warto zauważyć, że mogę łatwo uruchomić to, jeśli przestanę używać interfejsu i generycznych, ale wydaje mi się to idealną sytuacją do korzystania z interfejsu. Może staram się włożyć dużo rzeczy do interfejsu, gdy inna aplikacja (jak dziedziczenie bezpośrednie) może być lepsza?

Pytanie

Jak mogę zaimplementować interfejs z ogólnej wartości zwracanej przy jednoczesnym ograniczeniu typów, które mogą być zwracane tylko do implementacji innego interfejsu.

+0

Czy próbowałeś 'var returnList = nowa lista ();' –

+0

Tylko na uwadze, spójrz na swój TaskElement podczas wdrażania interfejsu, który musisz ustawić jako publiczny (domyślnie C# ustawia go jako prywatny) – ams4fy

+3

Nic wspólnego z twoim pytaniem, ale jeśli dopiero zaczynasz C# powinieneś przeczytać [konwencje kodowania] (https://msdn.microsoft.com/en-gb/library/ff926074.aspx) inni gracze będą wdzięczni ty! – Jamiec

Odpowiedz

3

Spójrzmy uważnie na swojej realizacji listItems:

public List<T> listItems<T>() where T : IElement 
{ 
    var returnList = new List<IElement> 

    foreach (var s in database.Table<TaskElement>()) 
    { returnList.Add(s); } 

    return returnList; 
} 

Co zrobiłeś tutaj opisana metoda, w której osoba dzwoniąca może poprosić o dowolny typ na liście, o ile ten typ implementuje IElement. Ale kod w twojej metodzie nie daje im listy typów, które chcą, daje im listę IElement. Więc to łamie umowę.

Ale prawdziwym źródłem problemu jest database.Table<TaskElement>(). To może tylko dać ci instancje TaskElement. Musisz zrobić to T, ale aby to zrobić trzeba dodatkowe ograniczenie Generic:

public List<T> listItems<T>() where T : IElement, new 
{ 
    var returnList = new List<T> 

    foreach (var s in database.Table<T>()) 
    { 
     returnList.Add(s); 
    } 

    return returnList; 
} 

To dlatego database.Table<T> ma new ograniczenia, co oznacza, że ​​może być podane tylko typy, które mają konstruktora bezparametrowego (ponieważ ta metoda stworzy instancje danej klasy).

+0

Świetnie, to działało. Całe moje kopanie i nigdy nie widziałem odniesienia do nowego. Nawiasem mówiąc, to musiał być 'Lista publiczna ListItems () gdzie T: IElement, new()' a także dodane do interfejsu tak, że umowa pomiędzy interfejsem i wdrażania została zachowana. To również uczyniło metodę całkowicie ogólną, co było czymś, co miałem zrobić jako drugi krok! Wielkie dzięki, @Kyle! –

0

Trzeba użyć uniwersalnym typem podczas tworzenia instancji obiektu:

Zamiast

var returnList = new List<IElement>(); 

Wykonaj

var returnList = new List<T>(); 
+0

To nie wystarczy, ponieważ nie można dodać instancji 'TaskElement' do' returnList'. – Lee

2

wierzę powinno być coś takiego

public List<T> listItems<T>() where T : IElement 
    { 
     var returnList = new List<T> 

     foreach (var s in database.Table<T>()) 
     { returnList.Add(s); } 

     return returnList; 
    } 
+0

Podałem, że strzał, ale niestety SQLiteConnection Tabela wymaga typu nie abstrakcyjnego. Właśnie dlatego chciałem użyć jawnego typu TaskElement (który jest zaimplementowany z IElement), ponieważ definiuje on tabelę SQL i jest wymagana przez metodę Table. –

+0

@NigelDH - jestem zdezorientowany, w jaki sposób sprawisz, że będzie to użyteczne - zakładając, że twoja klasa zwróci coś więcej niż "TaskElement", w jaki sposób zakodujesz, że ta metoda jest na tyle generyczna, aby zwrócić 'OtherElement' lub' ThirdElement', jeśli ' Przywołanie stołu wymaga konkretnego typu? – Jamiec

+0

Jesteś w rzeczywistości całkowicie poprawny. Robię bardziej statyczną implementację w taki sposób, aby uzyskać ogólne informacje na temat generycznych, ale najlepiej byłoby przekazać parametr do metody w następujący sposób: listItems (typ IElement), a następnie użyć go do określenia, która tabela jest wyszukiwana. Rozszerzyłbym to również o opcje wyszukiwania stołów itp. Staram się na razie zachować prostotę. –

1

Myślę, że jesteś na dobrej drodze z wyraźnie zdefiniowanie listy tak:

public interface IDatabase 
{ 
    void setItem(IElement task); //this works fine 
    List<IElement> listTasks<IElement>(); 
} 

Ponieważ nie można bezpośrednio cast Lista <TaskElement> do listy <IElement> trzeba będzie zrobić konwersję w swojej metodzie listTasks. Istnieje kilka metod zalecanych tutaj: Shorter syntax for casting from a List<X> to a List<Y>?. Myślę, że metoda LINQ to najprostszy, jeśli jesteś ok z LINQ:

List<IElement> listOfIElement = listOfTaskElement.Cast<IElement>().ToList()