Ковариантность и контравариантность в C#
Я начну с того, что я разработчик Java, который учится программировать на C#. Поэтому я сравниваю то, что знаю, с тем, что изучаю.
Я играю с C# generics в течение нескольких часов, и я смог воспроизвести те же вещи, которые я знаю на Java в C#, за исключением нескольких примеров с использованием ковариации и контравариации. Книги я читаю не очень хорошо в теме. Я, конечно, буду искать больше информации в интернете, но пока я это делаю это, возможно, вы можете помочь мне найти реализацию C# для следующего кода Java.
пример стоит тысячи слов, и я надеялся, что, посмотрев хороший образец кода, я смогу усвоить это быстрее.
ковариации
в Java я могу сделать что-то вроде этого:
public static double sum(List<? extends Number> numbers) {
double summation = 0.0;
for(Number number : numbers){
summation += number.doubleValue();
}
return summation;
}
Я могу использовать этот код следующим образом:
List<Integer> myInts = asList(1,2,3,4,5);
List<Double> myDoubles = asList(3.14, 5.5, 78.9);
List<Long> myLongs = asList(1L, 2L, 3L);
double result = 0.0;
result = sum(myInts);
result = sum(myDoubles)
result = sum(myLongs);
теперь я обнаружил, что C# поддерживает ковариантность/контравариантность только на интерфейсы и до тех пор, пока они были явно объявлены для этого (out/in). Я думаю, что я не смог воспроизвести этот случай, потому что я не мог найти общего предка всех чисел, но я считаю, что я мог бы использовать IEnumerable для реализации такой вещи, если общий предок существует. Поскольку IEnumerable является ковариантным типом. Правильно?
любые мысли о том, как реализовать приведенный выше список? Просто укажи мне правильное направление. Есть ли общий предок всех числовых типы?
контравариантность
пример контравариантность я попробовал следующее. В Java я могу сделать, это скопировать один список в другой.
public static void copy(List<? extends Number> source, List<? super Number> destiny){
for(Number number : source) {
destiny.add(number);
}
}
тогда я мог бы использовать его с контравариантными типами следующим образом:
List<Object> anything = new ArrayList<Object>();
List<Integer> myInts = asList(1,2,3,4,5);
copy(myInts, anything);
моя основная проблема, пытаясь реализовать это на C#, заключается в том, что я не смог найти интерфейс, который был бы ковариантным и контравариантным одновременно, как это имеет место в моем примере выше. Может быть, это возможно. сделано с двумя различными интерфейсами в C#.
есть мысли о том, как это реализовать?
большое спасибо всем за любые ответы, которые вы можете внести. Я уверен, что многому научусь на любом примере, который вы можете предоставить.
4 ответов
для 2-й части вашего вопроса вам не нужна контравариация, все, что вам нужно сделать, это заявить, что первый тип может быть приведен ко второму. Опять же, используйте where TSource: TDest
синтаксис для этого. Вот полный пример (который показывает как это сделать с помощью метода расширения):
static class ListCopy
{
public static void ListCopyToEnd<TSource, TDest>(this IList<TSource> sourceList, IList<TDest> destList)
where TSource : TDest // This lets us cast from TSource to TDest in the method.
{
foreach (TSource item in sourceList)
{
destList.Add(item);
}
}
}
class Program
{
static void Main(string[] args)
{
List<int> intList = new List<int> { 1, 2, 3 };
List<object> objList = new List<object>(); ;
ListCopy.ListCopyToEnd(intList, objList);
// ListCopyToEnd is an extension method
// This calls it for a second time on the same objList (copying values again).
intList.ListCopyToEnd(objList);
foreach (object obj in objList)
{
Console.WriteLine(obj);
}
Console.ReadLine();
}
вместо того, чтобы отвечать на ваши вопросы напрямую, я собираюсь ответить на несколько других вопросов:
нет в C# есть способ genericize за типы, которые поддерживают арифметические операторы?
не легко, нет. Было бы неплохо иметь возможность делать Sum<T>
метод, который может добавлять целые числа, двойники, матрицы, комплексные числа, кватернионы... и так далее. Хотя это довольно часто запрашиваемая функция, это также большая функция и он никогда не был достаточно высоким в списке приоритетов, чтобы оправдать его включение в язык. Я лично хотел бы этого, но вы не должны ожидать этого в C# 5. Возможно, в гипотетической будущей версии языка.
в чем разница между "сайт вызова в Java" ковариантность/контравариантность и C#'ы сайт "декларации" ковариантность/контравариантность?
фундаментальное различие на уровне реализации, конечно, это как практический вопрос, Генераторы Java реализуются через стирание; хотя вы получаете преимущества приятного синтаксиса для универсальных типов и проверки типов во время компиляции, вы не обязательно получаете преимущества производительности или преимущества системной интеграции типа времени выполнения, которые вы бы получили в C#.
но это скорее детали реализации. Более интересное отличие от моей точки зрения заключается в том, что правила дисперсии Java применяются локально и правила отклонения C#применяются .
то есть: некоторые преобразования вариантов опасны, потому что они подразумевают, что некоторые небезопасные операции не будут пойманы компилятором. Классический пример:
- тигр-Это млекопитающее.
- список X ковариантен в X. (предположим.)
- таким образом, список тигров-это список млекопитающих.
- список млекопитающих может быть вставлен жираф.
- поэтому вы можете вставьте жирафа в список тигров.
что явно нарушает безопасность типа, а также безопасность жирафа.
C# и Java используют два разных метода для предотвращения нарушения безопасности этого типа. C# говорит, что когда I<T>
объявлен интерфейс, если он объявлен как ковариантный, то не должно быть никакого метода интерфейса, который принимает T. Если нет метода для вставки в список, то вы никогда не будете вставлять жирафа в список тигров, потому что нет метода для вставки что-нибудь.
Java, напротив, говорит, что на этом локальном сайте мы получаем ковариантное отношение к типу, и мы обещаем не вызывать здесь никаких методов, которые могут нарушить безопасность типа.
у меня недостаточно опыта работы с функцией Java, чтобы сказать, что "лучше" при каких обстоятельствах. Техника Java, безусловно, интересна.
можно использовать IConvertible
интерфейс:
public static decimal sum<T>(IEnumerable<T> numbers) where T : IConvertible
{
decimal summation = 0.0m;
foreach(var number in numbers){
summation += number.ToDecimal(System.Globalization.CultureInfo.InvariantCulture);
}
return summation;
}
обратите внимание на общее ограничение (where T : IConvertible
), который похож на элемент extends
в Java.
нет базы Number
класс В. Сеть. Самое близкое, что вы можете получить, может выглядеть примерно так:
public static double sum(List<object> numbers) {
double summation = 0.0;
var parsedNumbers = numbers.Select(n => Convert.ToDouble(n));
foreach (var parsedNumber in parsedNumbers) {
summation += parsedNumber;
}
return summation;
}
вы должны были бы поймать любые ошибки, которые происходят во время Convert.ToDouble
в случае, если какой-либо объект в списке не является числовым и не реализует IConvertible
.
обновление
в этой ситуации, однако, я бы лично использовал IEnumerable
и общий тип ( и, благодаря Павел Как Tyng, вы можете силу T
для реализации IConvertible
):
public static double sum<T>(IEnumerable<T> numbers) where T : IConvertible {
double summation = 0.0;
var parsedNumbers = numbers.Select(n => Convert.ToDouble(n));
foreach (var parsedNumber in parsedNumbers) {
summation += parsedNumber;
}
return summation;
}