2012-05-11 6 views
16

To trochę długotrwałe pytanie, więc proszę o zachowanie mnie.Lista <> Func <> s, skompiluj błąd z ogólnym typem zwracania, ale dlaczego?

Potrzebuję utworzyć odwzorowanie między zestawami łańcuchów i odpowiednimi wywołaniami metod dla każdego ciągu. Jednak natknąłem się na problem z kompilacją, wyjaśniony niżej.

W moim scenariuszu używam Dictionary<>, ale problem istnieje tak samo dla List<>. Dla uproszczenia używam List<> w poniższym przykładzie.

Rozważmy te trzy klasy:

public abstract class MyBase { /* body omitted */ } 
public class MyDerived1 : MyBase { /* body omitted */ } 
public class MyDerived2 : MyBase { /* body omitted */ } 

i sposób w innej klasie:

public class Test 
{ 
    public T GetT<T>() where T : MyBase { /* body omitted */ } 
} 

W innej klasy, mogę zadeklarować List<Func<MyBase>> takiego:

public class SomeClass 
{ 
    public void SomeFunc() 
    { 
     var test = new Test(); 

     var list1 = new List<Func<MyBase>> 
      { 
       test.GetT<MyDerived1>, 
       test.GetT<MyDerived2> 
      }; 
    } 
} 

Wszystko w porządku i dobrze.

Ale co zrobić, jeśli chcę mieć funkcję powrotu rodzajowe klasy tak:

public class RetVal<T> where T : MyBase { /* body omitted */ } 

public class Test 
{ 
    public RetVal<T> GetRetValT<T>() where T : MyBase 
    { 
     return null; 
    } 
} 

I chcę utworzyć równoważny List<> użyciu tej funkcji. tj. listę >>?

public class Class1 
{ 
    public void SomeFunc() 
    { 
     var test = new Test(); 

     var list2 = new List<Func<RetVal<MyBase>>> 
      { 
       test.GetRetValT<MyDerived1>, // compile error 
       test.GetRetValT<MyDerived2> // compile error 
      }; 
    } 
} 

Dostaję błędy kompilacji Expected a method with 'RetVal<MyBase> GetRetValT()' signature.

Czy jest jakiś sposób obejścia tego problemu, czy istnieje alternatywne podejście, za pomocą którego mogę utworzyć mój ciąg ... generyczne wywołanie metody mapowania?

+1

Jest to kwestia wariancji. Zasadniczo 'RetVal ' * nie jest * a 'RetVal '. Więcej szczegółów, kiedy mam czas. –

+0

To, co powiedział @JonSkeet, jest prawdziwe. To prawo w języku C#. Jeśli chcesz wstawić 'RetVal ' s MyDerived do 'RetVal ' s MyBase, musisz sprawić, aby 'RetVal' zaakceptował również niepoprawny rodzaj, to jest' RetVal '. Zobacz przykład tutaj: http://www.ienablemuch.com/2012/05/generics-object-orientation-untyped.html –

+0

Mogę być całkowicie off-base na tym, ale nie byłby sam pomysł zwrotu ogólnej naruszają pojęcie, że generyczny musi (ostatecznie) rozwiązać coś, o czym wie kompilator w czasie kompilacji? To znaczy, powodem, dla którego kompilator zgłasza ten błąd, jest to, że oczekuje podania metody, która może zwrócić określony typ (co, jak rozumiem, jest przeciwieństwem tego, co próbujesz zrobić). Tylko spostrzeżenie, jak powiedziałem, może być daleko poza bazą ... –

Odpowiedz

16

C# pozwala tylko na kowariancję w interfejsach. Oznacza to, że nie można automatycznie odlutować urządzenia RetVal<MyDerived1> na urządzenie .Jeśli RetVal powinny być kowariantna utwórz interfejs dla niej tak:

public interface IRetVal<out T> 
{ 

} 
public class RetVal<T> : IRetVal<T> where T : MyBase { /* body omitted */ } 

public class Test 
{ 
    public IRetVal<T> GetRetValT<T>() where T : MyBase 
    { 
     return null; 
    } 
} 

Wtedy ten kod zadziała:

var list2 = new List<Func<IRetVal<MyBase>>> 
     { 
      test.GetRetValT<MyDerived1>, 
      test.GetRetValT<MyDerived2> 
     }; 
+0

Problemy jeśli użyjesz 'dataSources = nowa lista >>' w *** innym AppDomain ***: 'AppDomain.Create' – Kiquenet

+0

@Kiquenet: Nie rozumiem twojego komentarza. Możesz wyjaśnić? – StriplingWarrior

2

Niedawno miałem podobny problem.

C# nie obsługuje kowariancji typu powrotu w celu implementacji interfejsu lub nadpisania metody wirtualnej. Zobacz to pytanie, aby uzyskać szczegółowe informacje:

Does C# support return type covariance?.

Możesz być w stanie włamać to moja sprawka:

public RetVal<R> GetRetValT<T,R>() where T : MyBase where R : MyBase 
{ 
    return null; 
} 

//Then, change this to: 
test.GetRetValT<MyDerived1, MyBase> 
7

Problemem jest klasycznym kowariancji/kontrawariancja generyków. Zakładasz, że ponieważ MyDerived1 i MyDerived2 dziedziczy po MyBase, to RetVal<MyDerived1> dziedziczy po RetVal<MyBase>, a nie.

Najprostszym sposobem, aby naprawić to prawdopodobnie do zmiany kodu do: jeszcze lepiej

var list2 = new List<Func<RetVal<MyBase>>> 
     { 
      () => (MyBase)test.GetRetValT<MyDerived1>, 
      () => (MyBase)test.GetRetValT<MyDerived2> 
     }; 

lub, jak JS zwraca uwagę w komentarzach, wystarczy zmienić RetVal<T> być kowariantna jeśli to możliwe:

public interface IRetVal<out T> { ... } 

public class RetVal<T> : IRetVal<T> { ... } 
+3

Niezupełnie - w C# 4, 'Func ' * jest * covariant w T, dlatego pierwszy przykład działa. To 'RetVal ' który nie jest tutaj kowariantny. –

+1

Dobrze, przepraszam ... Wyprostuję to. –

3

Parametry ogólne Rodzaj dla klas nie może być kowariantna, ale mogą być kowariantna dla interfejsy. Możesz zrobić to, co chcesz, używając interfejsu IRetVal<T> zamiast klasy RetVal<T>, , jeśli parametr typu jest zadeklarowany jako kowariancja. Aby zadeklarować parametr typu interfejsu jako kowariancję, musi on być używany tylko w pozycjach "wyjściowych".

Aby zilustrować ten kod nie zostanie skompilowany:

interface IRetVal<out T> 
{ 
    T Value { get; } 
    void AcceptValue(T value); 
} 

Aby otrzymać kod do kompilowania, należy albo usunąć modyfikator out od parametru type lub usunąć metodę AcceptValue (ponieważ wykorzystuje T dla parametr: pozycja wejściowa).

Z interfejsu IRetVal<out T>, można to zrobić:

public class MyBase { } 
public class MyDerived1 : MyBase { } 
public class MyDerived2 : MyBase { } 

public interface IRetVal<out T> where T : MyBase { /* body omitted */ } 

public class Test 
{ 
    public IRetVal<T> GetRetValT<T>() where T : MyBase 
    { 
     return null; 
    } 
} 

public class Class1 
{ 
    public void SomeFunc() 
    { 
     var test = new Test(); 

     var list = new List<Func<IRetVal<MyBase>>> 
     { 
      test.GetRetValT<MyDerived1>, 
      test.GetRetValT<MyDerived2> 
     }; 
    } 
} 
Powiązane problemy