Ковариантность и Контравариантность в C # - PullRequest
13 голосов
/ 21 марта 2012

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

Я играю с дженериками C # уже несколько часов, и мне удалось воспроизвести те же вещи, которые я знаю в 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 ]

24 голосов
/ 21 марта 2012

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

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

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

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

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

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

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

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

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

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

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

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

7 голосов
/ 21 марта 2012

Для 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();
    }
4 голосов
/ 21 марта 2012

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

2 голосов
/ 21 марта 2012

В .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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...