2013-07-30 13 views
8

Kontekst: .NET 4.0, C#C# Korzystanie z generics i implementacja interfejsu

Tworzę zestaw interfejsów i zestaw klauzul implementujących je w celu świadczenia niektórych usług. Klienci używają konkretnych klase, ale metody wywołań, które są zadeklarowane za pomocą interfejsów jako typy parametrów.

uproszczony przykład jest to jedno:

namespace TestGenerics 
{ 
    // Interface, of fields 
    interface IField 
    { 
    } 

    // Interface: Forms (contains fields) 
    interface IForm<T> where T : IField 
    { 

    } 

    // CONCRETE CLASES 
    class Field : IField 
    { 
    } 

    class Form <T> : IForm<T> where T : IField 
    { 
    } 

    // TEST PROGRAM 
    class Program 
    { 
     // THIS IS THE SIGNATURE OF THE METHOD I WANT TO CALL 
     // parameters are causing the error. 
     public static void TestMethod(IForm<IField> form) 
     { 
      int i = 1; 
      i = i * 5; 
     } 

     static void Main(string[] args) 
     { 
      Form<Field> b = new Form<Field>(); 
      Program.TestMethod(b); 
     } 
    } 
} 

Kod sens dla mnie, ale pojawia się błąd kompilatora:

Argument 1: cannot convert from ' TestGenerics.Form<TestGenerics.Field> ' to ' TestGenerics.IForm<TestGenerics.IField> ' TestGenerics

Nie jestem pewien, co robię źle Przeczytałem wiele stron w Internecie, ale żaden nie rozwiązał mojego problemu.

Czy istnieje rozwiązanie, które nie będzie modyfikować że dużo architekturę co usiłuję zbudować:

Edit: I zaprojektowane interfejsy w taki sposób, że powinny one być niezależne od konkretnych clases które je wdrażają . Konkretne zdania mogą być ładowane z biblioteki dll, ale większość aplikacji działa z interfejsami. W niektórych przypadkach potrzebuję użyć konkretnych klauzul, szczególnie gdy używam klauzul, które wymagają serializacji.

Z góry dziękuję.

Alejandro

+5

Byłoby to wchodzą w dziedzinie kowariancji. W szczególności, jeśli dodasz modyfikator ogólny "out" (http://msdn.microsoft.com/en-us/library/dd469487.aspx) do swojego interfejsu sygnaturowego IForm , gdzie T: "IField" to będzie działać .Dodaje to jednak inne ograniczenia/uwagi, więc nie mogę komentować, jeśli ma to zastosowanie do obecnego projektu/użycia. –

+2

Jestem ciekawa, co próbujesz osiągnąć dzięki temu projektowi. – sidesinger

+0

Wielkie dzięki dla obu anwersów, oni byli naprawdę niewiarygodni. Dodałem edycję wyjaśniającą, dlaczego zaprojektowałem rozwiązanie w ten sposób, ale oba rozwiązania mogą rozwiązać mój problem. pozdrowienia. – Sugar

Odpowiedz

13

Problemem jest to, że Form<Field> realizuje IForm<Field> ale nie IForm<IField>. Nie możesz użyć odziedziczonej klasy (lub interfejsu) jako parametru ogólnego, chyba że jest ona oznaczona jako kowariancyjna z identyfikatorem out. Jednak oznaczenie interfejsu jako kowariant znacznie ograniczy jego użycie (zasadniczo tworząc interfejs "tylko wyjściowy", taki jak IEnumerable), więc może nie działać dla ciebie.

Jednym ze sposobów, aby zmusić go do pracy jest, aby TestMethod rodzajowe, a także:

public static void TestMethod<T>(IForm<T> form) where T:IField 
{ 
    int i = 1; 
    i = i * 5; 
} 
10

Można użyć kowariancji, tak:

interface IForm<out T> where T : IField 
{ 

} 

Więcej o kowariancji i kontrawariancji here.

+1

Kowariancja po zezwoleniu na użycie klasy pochodnej jako ogólnego rodzaju parametru za pomocą słowa kluczowego "out". W zamieszczonym przeze mnie linku można zobaczyć to zdanie: "ponieważ parametr typu interfejsu IEnumerable jest kowariancyjny, można przypisać instancję IEnumerable (IEnumerable (Of Derived) w języku Visual Basic) do zmiennej typu IEnumerable " –

7

Inni zwrócili uwagę na uzasadnienie komunikatu o błędzie, ale przyjrzyjmy się projektowi przykładowego kodu na chwilę. Być może używasz generycznych, gdzie żadna nie jest potrzebna.

Powiedziałeś już, że używasz metod zadeklarowanych w interfejsie IField, więc nie ma potrzeby, aby twoja klasa IForm była generyczna - po prostu przechowuj odwołania do IField, zamiast ogólnego argumentu "T" (który już jest gwarantowany i tak już jest IField).

Na przykład, można użyć:

public interface IForm 
{ 
    IEnumerable<IField> Fields { get; set; } 
} 

zamiast

public interface IForm<T> where T : IField 
{ 
    IEnumerable<T> Fields { get; set; } 
} 
+0

Uzgodnione. Innym wskazaniem jest to, że kod konsumenta tworzył ten formularz jako IForm i Form , co sugeruje, że IField jest wystarczający. Prawdziwym testem jest, czy nie, beton typu T musi być częścią zewnętrznego API. – Tormod