Общий метод обрабатывает 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>();
Затем я хотел бы использовать оба класса Counter в коллекции.
var specific = new Counter<CItem>();
var nonspecific = new Counter();
specific.Count(countables);
nonspecific.Count(countables);
Конкретный счетчик распознает, что коллекция счетчиков должна попадать в подпись int Count (IEnumerable), но неконкретная версия этого не делает. Я получаю сообщение об ошибке:
Тип 'System.Collections.Generic.List<CItem>
' не может использоваться как тип типа T
'в общем типе или методе Counter.Count<T>(T)
. Нет никакого неявного преобразования ссылок из List<CItem>
'до ICountable
.
Похоже, что неспецифическая версия использует неправильную подпись для коллекции.
Почему они ведут себя по-другому?
Как указать конкретную версию, чтобы вести себя так же, как и другая?
Примечание. Я знаю, что этот пример нереалистичен. Однако я столкнулся с этой проблемой в довольно сложном сценарии с методами расширения. Я использую эти классы для простоты
Заранее спасибо
Ответы
Ответ 1
Проблема с неспецифическим классом заключается в том, что компилятор не знает тип 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));
}
Выход: "метод для одного элемента"
Ответ 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)
, а первый выигрывает разрешение перегрузки.
Ответ 3
По моему мнению, причина, по которой компилятор считает, что вы вызываете Counter.Count(T) вместо Counter.Count <T> (IEnumerable <T> ) объясняется тем, что для более позднего требуется преобразование из списка в IEnumerable. И это имеет приоритет меньше, чем использование прежней сигнатуры Counter.Count(T), которая приводит к ошибке.
Я думаю, что лучше изменить имя метода того, что принимает IEnumerble в качестве аргумента в нечто вроде CountAll. Что-то, что .NET Framework делает для List.Remove и List.RemoveAll. Это хорошая практика, чтобы сделать ваш код более конкретным, а не позволять компилятору выполнять все решения.