Ковариантность и контравариантность в 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;
}