Я читал интервью с Joshua Bloch в Кодерах на Работе, где он оплакивал введение дженериков в Java 5. Ему не нравится определенная реализация в основном, потому что поддержка различия — подстановочные знаки Java — делают ее излишне сложной.
Насколько я знаю, C# 3 не имеет ничего как явные, ограниченные подстановочные знаки, например, Вы не можете объявить метод PriceBatch, который берет набор Актива или любого подкласса Актива (void PriceBatch(Collection<? extends Asset> assets)
в Java?).
Кто-либо знает, почему подстановочные знаки и границы не были добавлены к C#? Эти функции были намеренно не учтены для создания языка более простым, или это что-то, что они просто не обошли для реализации уже?
Править: Святой дым, комментирует от самого Eric Lippert! После чтения проницательных комментариев его и Paul я понимаю, что, по крайней мере, верхние границы поддерживаются и что вышеупомянутый пример может быть переведен в C# как:
void PriceBatch<T>(ICollection<T> assets) where T : Asset
Нижние границы, с другой стороны, по-видимому, не поддерживаются, как Eric говорит в своем втором комментарии, например, нет, вероятно, никакого способа непосредственно перевести это (несколько изобретенный) код Java к C#:
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);
Я корректен? Если это верно, есть ли конкретная причина, почему C# имеет верхний, но не нижние границы?
Сложный вопрос.
Сначала рассмотрим ваш фундаментальный вопрос: "Почему это запрещено в C#?"
class C<T> where T : Mammal {} // legal
class D<T> where Giraffe : T {} // illegal
То есть, общее ограничение типа может сказать: "T должно быть любым типом ссылки, который может быть присвоен переменной типа Mammal", но не "T должно быть любым типом ссылки, переменной которого может быть присвоен жираф". Почему такая разница?
Я не знаю. Это было задолго до того, как я попал в команду C#. Тривиальный ответ - "потому что CLR его не поддерживает", но команда, которая разработала C# genics, была той же командой , которая разработала CLR genics, так что это на самом деле не так уж и много объяснений.
Мое предположение было бы просто, что как всегда, для того, чтобы функция была поддержана, она должна быть спроектирована, реализована, протестирована, документирована и отправлена заказчикам; никто никогда не делал ничего из этого для этой функции, и поэтому она не на языке. Я не вижу больших, неоспоримых преимуществ предлагаемой функции; сложные функции без неоспоримых преимуществ, как правило, сокращаются здесь.
Однако, это предположение. В следующий раз, когда я буду общаться с ребятами, которые работали над дженериками - они живут в Англии, так что, к сожалению, не похоже, чтобы они были внизу по коридору от меня - я спрошу.
Что касается вашего конкретного примера, я думаю, что Пол прав. Вам не нужны нижние ограничения, чтобы это сработало на C#. Можно сказать:
void Copy<T, U>(Collection<T> src, Collection<U> dst) where T : U
{
foreach(T item in src) dst.Add(item);
}
То есть, поставить ограничение на T, а не на U.
.C# 4 вводит новые возможности, которые позволяют использовать ковариацию и контрвариантность в дженериках.
Есть и другие SO-посты, которые говорят об этом более подробно: Как в C# 4.0 реализована ковариативность и контрастность генерических аргументов?
Новая возможность не включает ее автоматически во всех типах, но появился новый синтаксис, позволяющий разработчикам указывать, являются ли генерические аргументы ковариантными или контрвариантными.
C# версии до C# 4 имели ограниченную функциональность, подобную той, что относится к делегатам и определенным типам массивов. Что касается делегатов, то разрешены делегаты, которые принимают типы базовых параметров. Что касается типов массивов, Я думаю, что это действительно, если только бокс не связан с этим. То есть, массив Заказчика может быть случайным для массива объектов. Однако массив ints не может быть приведен к массиву объектов.
..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.
Однако я не в состоянии ответить на реальный вопрос!
.