С# generics - без нижних границ по дизайну?

Я читал интервью с Джошуа Блохом в "Coders at Work", где он жаловался на введение дженериков на Java 5. Ему не нравится конкретная реализация в значительной степени из-за того, что поддержка дисперсии - подстановочные символы Java - делает ее излишне сложной.

Насколько я знаю, С# 3 не имеет ничего похожего на явные ограниченные подстановочные знаки, например. вы не можете объявить метод PriceBatch, который принимает коллекцию Asset или любого подкласса Asset (void PriceBatch(Collection<? extends Asset> assets) в Java?).

Кто-нибудь знает, почему подстановочные знаки и ограничения не были добавлены в С#? Были ли эти функции умышленно упущены, чтобы сделать язык более простым, или это то, чего они еще не успели реализовать?

EDIT: Священный дым, комментарии самого Эрика Липперта! После прочтения его проницательных замечаний и Пол я понимаю, что поддерживаются, по крайней мере, верхние границы и что приведенный выше пример можно перевести на С# следующим образом:

void PriceBatch<T>(ICollection<T> assets) where T : Asset

Нижние границы, с другой стороны, по-видимому, не поддерживаются, как говорит Эрик в своем втором комментарии, например. вероятно, нет способа напрямую перевести этот (несколько надуманный) Java-код на С#:

public class Asset {}
public class Derivative extends Asset {}
public class VanillaOption extends Derivative {}

public static <T extends Asset> void copyAssets(Collection<T> src, Collection<? super T> dst) {
    for(T asset : src) dst.add(asset);
}

Collection<VanillaOption> src = new ArrayList<VanillaOption>();
[...]
Collection<Derivative> dst = new ArrayList<Derivative>();
[...]
copyAssets(src, dst);

Правильно ли я? Если это так, есть ли особая причина, почему С# имеет верхние, но не нижние границы?

Ответ 1

Сложный вопрос.

Сначала рассмотрим ваш фундаментальный вопрос: "Почему это незаконно в С#?"

class C<T> where T : Mammal {} // legal
class D<T> where Giraffe : T {} // illegal

То есть ограничение общего типа может означать, что "T должен быть любым ссылочным типом, который может быть назначен переменной типа Mammal", но не "T должен быть любым ссылочным типом, переменной которого может быть назначен Жираф". Почему разница?

Я не знаю. Это было задолго до моего времени в команде С#. Тривиальный ответ - "потому что CLR его не поддерживает", но команда, которая разработала генераторы С#, была той же командой, которая разрабатывала общие средства CLR, так что это действительно не очень объяснение.

Мое предположение будет состоять в том, что, как всегда, чтобы поддерживать, функция должна быть спроектирована, реализована, протестирована, документирована и отправлена ​​клиентам; никто никогда не делал ничего из этого для этой функции, и поэтому это не на языке. Я не вижу большого, неотразимого преимущества предлагаемой функции; сложные функции без каких-либо убедительных преимуществ, как правило, обрезаются здесь.

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

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

void Copy<T, U>(Collection<T> src, Collection<U> dst) where T : U 
{ 
    foreach(T item in src) dst.Add(item);
}

То есть, поставьте ограничение на T, а не на U.

Ответ 2

В С# 4 представлены новые функции, которые допускают ковариацию и контравариантность в дженериках.

Есть другие сообщения SO, которые говорят об этом более подробно: Как генерируется общая ковариация и contra-дисперсия в С# 4.0?

Новая функция не включает это автоматически во всех типах, но есть новый синтаксис, который позволяет разработчикам определять, являются ли общие аргументы ковариантными или контравариантными.

Версии С# до С# 4 имели ограниченную функциональность, подобную этому, поскольку она относится к делегатам и некоторым типам массивов. В отношении делегатов допускаются делегаты, которые принимают базовые типы параметров. Что касается типов массивов, я считаю это действительным, если не будет задействован бокс. То есть массив Customer может быть случайным для массива объекта. Тем не менее, массив ints нельзя отнести к массиву объектов.

Ответ 3

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

namespace ConsoleApplication3
{
    class Program
    {
        static void Main(string[] args)
        {
         List<a> a = new List<a>();
         List<b> b = new List<b>();
         List<c> c = new List<c>();
         test(a);
         test(b);
         test(c);

        }

        static void test<T>(List<T> a) where T : a
        {
            return;
        }
    }
    class a
    {

    }
    class b : a
    {

    }
    class c : b
    {

    }
}

Пример 2

namespace ConsoleApplication3
{
    class Program
    {
        static void Main(string[] args)
        {
            ICollection<VanillaOption> src = new List<VanillaOption>();
        ICollection<Derivative> dst = new List<Derivative>();
         copyAssets(src, dst);
        }

        public static void copyAssets<T,G>(ICollection<T> src, ICollection<G> dst) where T : G {
            foreach(T asset in src) 
                dst.Add(asset);
        }
    }
    public class Asset {}
    public class Derivative : Asset {}
    public class VanillaOption : Derivative {}
}

В этом примере представлено преобразование кода из вашего примера в java.

Я действительно не в состоянии ответить на реальный вопрос, хотя!