2015-12-16 6 views
10

Пожалуйста, проверьте следующие коды сегментов:Общий метод обрабатывает IEnumerable иначе, чем общий тип

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; 
    } 
} 

Две версии счетчика отличаются только в описании общего параметра. Один из них определяет как типичный параметр типа, другой - как общий аргумент. Оба ограничивают аргументы метода для реализации интерфейса ICountable. Я буду называть их конкретных и неспецифических соответственно.

Теперь я определяю класс, который реализует интерфейсICountable и коллекцию экземпляров:

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

Тогда, я хотел бы использовать оба класса Счетчик на коллекции.

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

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

Специальный счетчик признает, что countables коллекция должна попадать в подписи Int Count (IEnumerable), но неспецифическая версия не делает. Я получаю сообщение об ошибке:

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 .

Похоже, что нестандартная версия использует неправильную подпись для коллекции.

Почему они ведут себя по-другому? Как указать конкретную версию, чтобы вести себя так же, как и другая?

Примечание: Я знаю, что этот пример нереалистичен. Однако я столкнулся с этой проблемой в довольно сложном сценарии с методами расширения. Я использую эти классы для простоты

Заранее спасибо

+2

, если указать общий тип явно на как 'nonspecific.Count (countables)', которые будут работать слишком –

+1

, если вы определяете '' countables' с IEnumerable countables = новый список () 'выбрана правильная перегрузка , – SWeko

+1

Перегрузки метода Count() неоднозначны, оба могут принимать список в качестве аргумента. Однако один из них нарушает ограничение в * конкретном * случае передачи списка. Это не очень хорошо, если вы используете другой тип, который реализует IEnumerable * и * ICountable, то это действительно двусмысленно. C# не позволяет объявлять общий тип/метод, который может случайно не скомпилироваться. –

ответ

4

Проблемы с неспецифическим классом является то, что компилятор не знает тип T во время компиляции, поэтому он не может выбрать правильную перегрузку для метода Count<T>(). Однако, если вы задаете общие ограничения типов, компилятор теперь знает, какого типа ожидать ...

Если вы закомментируете свой метод с подписями public int Count<T>(T Item), он скомпилируется, потому что он будет использовать метод с правильной сигнатурой (то есть public int Count<T>(IEnumerable<T> items))

Это будет также компилировать и запускать, если помочь компилятору вывести тип литья вашего списка IEnumerable<CItem> явно:

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

Посмотри упрощенный сценарий:

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)); 
    } 

Выход: «метод одного элемента»

+0

Да, я испытал то же самое. В моем сценарии у меня есть разные методы расширения для коллекций и отдельные экземпляры того же типа. Я ожидаю, что компилятор будет выбирать подпись с параметром коллекции, когда я передаю коллекцию и подпись с параметром экземпляра, когда я передаю экземпляр значка. Это нереалистичное ожидание? –

+0

@ DanielLeiszen Да, потому что это так, как работают общие параметры в C#. Компилятор не знает тип во время компиляции, только во время выполнения ... Однако, если вы устанавливаете ограничения типа, вы говорите компилятору, какой тип он должен ожидать. – Fabjan

2

Если я правильно помню (будет пытаться найти ссылки в описании), метод T выбран потому, что это точно соответствует типу.

Тип вывода правильно определяет, что оба общих метода применимы, как Count<CItem>(IEnumerable<CItem> items) и Count<List<CItem>>(List<CItem> items).Однако первая потеряет в разрешении перегрузки, так как вторая более конкретна. После этого ограничения вступают в игру, поэтому вы получаете ошибку времени компиляции.

Если вы объявляете ваш countables используя

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

, то выбор становится Count<CItem>(IEnumerable<CItem> items) и Count<IEnumerable<CItem>>(IEnumerable<CItem> items) и первый один выигрывает разрешение перегрузки.

+1

Спасибо за сообщение. Я заметил решение Фабьяна как ответ, так как это было первым. Но ваш пост также отвечает на вопрос. –

+0

@ DanielLeiszen, не проблема, это был интересный вопрос - теперь мне нужно прорыть спецификацию, чтобы найти, почему она работает конкретно во втором случае. – SWeko

1

На мой взгляд, причина, по которой компилятор считает, что вы вызываете Counter.Count (T) вместо Counter.Count < T> (IEnumerable < T>) объясняется тем, что для более позднего требуется преобразование из списка в IEnumerable , И это имеет приоритет меньше, чем использование прежней сигнатуры Counter.Count (T), которая приводит к ошибке.

Я думаю, что лучше изменить имя метода того, что принимает IEnumerble в качестве аргумента в нечто вроде CountAll. Что-то, что .NET Framework делает для List.Remove и List.RemoveAll. Это хорошая практика, чтобы сделать ваш код более конкретным, а не позволять компилятору выполнять все решения.

 Смежные вопросы

  • Нет связанных вопросов^_^