2013-03-19 11 views
10

Chciałbym móc zrobić coś takiego:Dlaczego nie mogę używać kompatybilnego rodzaj betonu przy wdrażaniu interfejs

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

namespace Test 
{ 
    public interface IFoo 
    { 
     IEnumerable<int> integers { get; set; } 
    } 

    public class Bar : IFoo 
    { 
     public List<int> integers { get; set; } 
    } 
} 

Dlaczego kompilator narzeka ..?

Error 2 'Test.Bar' does not implement interface member 'Test.IFoo.integers'. 'Test.Bar.integers' cannot implement 'Test.IFoo.integers' because it does not have the matching return type of 'System.Collections.Generic.IEnumerable<int>'. 

Rozumiem, że interfejs mówi IEnumerable i klasa korzysta z listy, ale Lista jest IEnumerable .....

co mogę zrobić? Nie chcę określać IEnumerable w klasie, chcę użyć typu betonu, który implementuje IEnumerable, jak List ...

+2

„Chcę wykorzystać konkretny typ, który implementuje IEnumerable , jak List ... "- dlaczego? – jcollum

Odpowiedz

12

Jest to problem kowariancji/kontrawariancji typu (patrz http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)#C.23).

Istnieje obejście: używać jawnych interfejsy, tak jak poniżej:

public class Bar : IFoo { 

    private IList<int> _integers; 

    IEnumerable<int> IFoo.integers { 
     get { return _integers }; 
     set { _integers = value as IList<int>; } 
    } 

    public IList<int> integers { 
     get { return _integers; } 
     set { _integers = vale; } 
    } 
} 

Zauważ, że integers należy TitleCased w celu dostosowania się do wytycznych w .NET.

Mam nadzieję, że możesz zobaczyć problem w powyższym kodzie: IList<int> jest kompatybilny z IEnumerable<int> tylko dla akcesora, ale nie dla ustawienia. Co się stanie, jeśli ktoś zadzwoni pod numer IFoo.integers = new Qux<int>() (gdzie Qux : IEnumerable<int>, ale nieQux : IList<int>).

+0

Okazuje się to niezwykle przydatne przy rozwiązywaniu problemu deserializacji obiektów JSON w konkretny obiekt, który musi mieć parametry interfejsu. Poprzez podanie konkretnych parametrów, deserializator może znaleźć sposób na utworzenie instancji obiektu dla parametrów. –

4

Mimo że lista implementuje IEnumerable, to nie tak działają interfejsy. Interfejs określa dokładnie, które typy muszą być wystawione na właściwości. Jeśli utworzono rodzajowy interfejs jak

public interface IFoo<T> where T : IEnumerable<int> 
{ 
    T integers { get; set; } 
} 

Można wtedy użyć IFoo<List<int>> wdrożyć go w sposób, można by oczekiwać.

3

Nie będziesz mógł użyć konkretnego typu, chyba że zrobisz to za kulisami. Problem polega na tym, że możesz pobrać i ustawić właściwość.

Twój interfejs określa, że ​​właściwość ma typ IEnumerable<int>. HashSet<int> implementuje IEnumerable<int>. Oznacza to, że następujące powinny działać dobrze:

IFoo instance = new Bar(); 
instance.integers = new HashSet<int>(); 

Ale ponieważ starasz się implementować interfejs przy użyciu betonu typu List<int>, nie ma mowy, że zadanie może pracować.

Najprostszym fix, zakładając, że nie stale trzeba ponownie przypisać kolekcję, byłoby tylko określić getter dla kolekcji:

public interface IFoo 
{ 
    IEnumerable<int> Integers { get; } 
} 

public class Bar 
{ 
    public List<int> Integers { get; private set; } 

    public Bar(List<int> list) 
    { 
     Integers = list; 
    } 
} 
+0

Zakładam, że to działa (nie mogę tego teraz przetestować), ale nie rozumiem dlaczego. Dlaczego usunięcie settera z interfejsu pozwoli 'List ' zrealizować 'IEnumerable '? Czy kompilator sprawdza poprawność w każdym kierunku (tj.możesz zaimplementować 'List Integers {set; } 'z' IEnumerable Integers {private get; ustaw} '? – Bobson

+0

Zakładam, że 'Bar' w przykładowym kodzie implementuje' IFoo'? Jeśli tak, kod nie rozwiązuje problemu. Ten sam błąd kompilacji: 'Test.Bar.Integers 'nie może zaimplementować Test.IFoo.Integers, ponieważ nie ma zgodnego typu zwrotu" System.Collections.Generic.IEnumerable ". Czy czegoś brakuje? – Peter

Powiązane problemy