Подтвердить что ты не робот

Ковариация и контравариантность в С#

Начну с того, что я являюсь разработчиком Java для программирования на С#. Поэтому я сравниваю то, что знаю с тем, чему я учусь.

Я играю с генераторами С# в течение нескольких часов, и я смог воспроизвести те же самые вещи, которые я знаю в Java на С#, за исключением нескольких примеров, использующих ковариацию и контравариантность. Книга, которую я читаю, не очень хороша в этом вопросе. Я обязательно буду искать дополнительную информацию в Интернете, но пока я это делаю, возможно, вы можете помочь мне найти реализацию С# для следующего кода 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);

Теперь я обнаружил, что С# поддерживает ковариацию/контравариантность только на интерфейсах и до тех пор, пока они явно объявлены для этого (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);

Моя основная проблема, пытаясь реализовать это на С#, заключается в том, что я не мог найти интерфейс, который одновременно был ковариантным и контравариантным, так как это случай List в моем примере выше. Возможно, это может быть сделано с двумя различными интерфейсами в С#.

Любые мысли о том, как реализовать это?

Благодарим всех за любые ответы, которые вы можете внести. Я уверен, что я буду многому научиться из любого примера, который вы можете предоставить.

4b9b3361

Ответ 1

Для второй части вашего вопроса вам не нужна контравариантность, все, что вам нужно сделать, - это указать, что первый тип можно отбросить ко второму. Снова используйте синтаксис 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();
    }

Ответ 2

Вместо того, чтобы отвечать на ваши вопросы напрямую, я собираюсь ответить на несколько несколько разные вопросы:

Есть ли у С# способ обобщения типов, поддерживающих арифметические операторы?

Не легко, нет. Было бы неплохо иметь способ создания метода Sum<T>, который мог бы добавлять целые числа, парные, матричные, комплексные числа, кватернионы... и так далее. Хотя это довольно часто запрашиваемая функция, она также является большой функцией, и она никогда не была достаточно высокой в ​​списке приоритетов, чтобы оправдать ее включение в язык. Мне это очень понравилось, но вы не должны ожидать этого в С# 5. Возможно, в гипотетической будущей версии языка.

В чем разница между ковариацией/контравариантностью и сайтом С# "коллаборацией/контравариантностью" сайта "call site" Java?

Фундаментальное различие на уровне реализации - это, конечно, что, как практический вопрос, Java-дженерики реализуются посредством стирания; хотя вы получаете преимущества приятного синтаксиса для типичных типов и проверки типа времени компиляции, вы не обязательно получаете преимущества производительности или преимущества интеграции системы времени выполнения, которые вы бы на С#.

Но это действительно больше деталей реализации. Более интересное отличие от моей перспективы заключается в том, что правила Java-дисперсии применяются локально, а правила отклонения С# применяются глобально.

То есть: некоторые варианты конверсий опасны, потому что они подразумевают, что некоторые небезопасные операции не будут улавливаться компилятором. Классический пример:

  • Тигр - это млекопитающее.
  • Список X ковариантен в X. (Предположим.)
  • Список тигров - это список млекопитающих.
  • Список млекопитающих может содержать жирафа.
  • Поэтому вы можете вставить жирафа в список тигров.

Что явно нарушает безопасность типа, а также безопасность жирафа.

С# и Java используют два разных метода для предотвращения нарушения безопасности этого типа. С# говорит, что когда объявлен интерфейс I<T>, если он объявлен как ковариант, тогда не должно быть никакого метода интерфейса, который принимает в T. Если нет способа вставки T в список, тогда вы никогда не будете вставьте жирафа в список тигров, потому что нет способа вставить что-либо.

Java, напротив, говорит, что на этом локальном сайте мы относимся к типу ковариантно, и мы обещаем не называть какие-либо методы прямо здесь, что может нарушить безопасность типов.

У меня недостаточно опыта работы с Java, чтобы сказать, что "лучше" при каких обстоятельствах. Техника Java, безусловно, интересна.

Ответ 3

Вы можете использовать интерфейс 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.

Ответ 4

В .NET нет базового класса 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 и общий тип (и, благодаря Paul 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;
}