2015-12-16 15 views
10

proszę sprawdzić następujące segmenty kodów:metoda Generic obsługuje IEnumerable inaczej niż typu rodzajowego

public interface ICountable { } 
public class Counter<T> 
    where T : ICountable 
{ 
    public int Count(IEnumerable<T> items) 
    { 
     return 0; 
    } 

    public int Count(T Item) 
    { 
     return 0; 
    } 
} 

public class Counter 
{ 
    public int Count<T>(IEnumerable<T> items) 
     where T : ICountable 
    { 
     return 0; 
    } 

    public int Count<T>(T Item) 
     where T : ICountable 
    { 
     return 0; 
    } 
} 

Obie wersje różnią się tylko w Counter specyfikacji parametru rodzajowego. Jeden z nich definiuje jako typowy parametr, drugi jako ogólny argument. Oba ograniczają argumenty metody do implementacji interfejsu ICountable. Nazwiemy je odpowiednio specyficznymi i niespecyficznymi.

Teraz jestem zdefiniowanie klasy, która implementuje interfejsICountable oraz kolekcję przypadkach:

public class CItem : ICountable { } 
var countables = new List<CItem>(); 

Następnie chciałbym używać obu klas licznik na zbiorach.

var specific = new Counter<CItem>(); 
var nonspecific = new Counter(); 

specific.Count(countables); 
nonspecific.Count(countables); 

Specyficzny licznik uznaje, że countables kolekcja powinna spaść do podpisu int Count (IEnumerable), ale niespecyficzne wersja nie robi. Wystąpił błąd:

The type ' System.Collections.Generic.List<CItem> ' cannot be used as type parameter ' T ' in the generic type or method ' Counter.Count<T>(T) '. There is no implicit reference conversion from List<CItem> ' to ICountable .

Wygląda na to, że wersja nieokreślona używa niewłaściwego podpisu do kolekcji.

Dlaczego zachowują się inaczej? W jaki sposób można określić nieokreśloną wersję, aby zachowywać się tak samo jak inne?

Uwaga: Wiem, że ten przykład nie jest realistyczny. Jednak napotkałem ten problem w dość skomplikowanym scenariuszu z metodami rozszerzenia. Używam tych klas dla uproszczenia

góry dzięki

+2

jeśli typ rodzajowy wyraźnie na jak 'nonspecific.Count (countables)', które będą pracować zbyt –

+1

jeśli zdefiniować '' countables' z IEnumerable countables = new List (); 'poprawna przeciążenie jest wybierany . – SWeko

+1

Dwa przeciążenia metody Count() są niejednoznaczne, oba mogą przyjąć List jako argument. Jednak jeden z nich narusza ograniczenie w * specyficznym * przypadku przekazania listy. To nie wystarczy, jeśli użyjesz innego typu, który implementuje zarówno IEnumerable * i * ICountable, to jest naprawdę niejednoznaczny. C# nie pozwala zadeklarować typowego typu/metody, które mogą losowo nie skompilować. –

Odpowiedz

4

Problem z nieswoistym klasy jest to, że kompilator nie zna typu T w czasie kompilacji, dlatego nie można wybrać prawidłową przeciążenie dla metody Count<T>(). Jeśli jednak ustawisz typowy kompilator ograniczeń typu, teraz wiesz, jakiego typu spodziewać się ...

Jeśli skomentujesz swoją metodę z podpisem public int Count<T>(T Item), to się skompiluje, ponieważ użyje metody z poprawnym podpisem (która jest public int Count<T>(IEnumerable<T> items))

będzie również skompilować i uruchomić, jeśli kompilator pomóc wywnioskować typ oddając swój List do IEnumerable<CItem> wyraźnie:

nonspecific.Count(countables as IEnumerable<CItem>); 

Wystarczy popatrzeć na uproszczony scenariusz:

static string A<T>(IEnumerable<T> collection) 
    { 
     return "method for ienumerable"; 
    } 

    static string A<T>(T item) 
    { 
     return "method for single element"; 
    } 

    static void Main(string[] args) 
    { 
     List<int> numbers = new List<int>() { 5, 3, 7 }; 
     Console.WriteLine(A(numbers)); 
    } 

wyjściowa: „metoda dla pojedynczego elementu”

+0

Tak, doświadczyłem tego samego. W moim scenariuszu mam różne metody rozszerzenia dla kolekcji i pojedynczych instancji tego samego typu. Oczekuję, że kompilator wybierze sygnaturę z parametrem kolekcji, gdy przekazuję kolekcję i sygnaturę z parametrem instancji, gdy przekażę instancję podpisu. Czy to nierealistyczne oczekiwanie? –

+0

@DanielLeiszen Tak, ponieważ tak działają ogólne parametry w kompilatorze C#, nie znają typu podczas kompilacji, tylko w czasie wykonywania ... Jednak jeśli ustawisz ograniczenia typów, "powiesz kompilatorowi" jakiego typu powinien się spodziewać. – Fabjan

2

Jeśli dobrze pamiętam (postara się znaleźć odniesienie w specyfikacji), metoda T jest wybrany, ponieważ jest to dokładne dopasowanie dla danego typu.

Wnioskowanie o typ, prawidłowo identyfikuje, że obie metody ogólne mają zastosowanie, jako Count<CItem>(IEnumerable<CItem> items) i Count<List<CItem>>(List<CItem> items).Jednak pierwszy z nich traci w wyniku przeciążenia, ponieważ drugi jest bardziej szczegółowy. Ograniczenia pojawiają się w grze dopiero po tym, więc dostajesz błąd czasu kompilacji.

Jeśli zadeklarować countables użyciu

IEnumerable<CItem> countables = new List<CItem>(); 

następnie wybór staje Count<CItem>(IEnumerable<CItem> items) i Count<IEnumerable<CItem>>(IEnumerable<CItem> items) i pierwszy wygrywa rozdzielczość przeciążeniem.

+1

Dzięki za wpis. Napisałem rozwiązanie Fabjana jako odpowiedź, ponieważ to było pierwsze. Ale Twój post odpowiada również na pytanie. –

+0

@DanielLeiszen, nie ma problemu, to było interesujące pytanie - teraz muszę przejrzeć specyfikację, aby znaleźć powód, dla którego działa ona w drugim przypadku. – SWeko

1

Moim zdaniem powodem kompilator uważa, że ​​dzwonisz Counter.Count (t) zamiast Counter.Count < T> (IEnumerable < T>) jest dlatego, że później wymaga konwersji z listy, aby IEnumerable . A to ma mniejszy priorytet niż użycie poprzedniej sygnatury Counter.Count (T), co powoduje błąd.

Myślę, że lepiej jest zmienić nazwę metody, która przyjmuje IEnumerble jako argument na coś w rodzaju CountAll. Pewna struktura .NET działa dla List.Remove i List.RemoveAll. Dobrą praktyką jest uczynienie kodu bardziej szczegółowym, niż pozwolenie kompilatorowi na podejmowanie wszystkich decyzji.

Powiązane problemy