C# дженерики - без ограничений по дизайну?
Я читал интервью с Джошуа Блохом в журнале "Coders at Work", где он посетовал на введение обобщений в Java 5. Он не очень любит конкретную реализацию, потому что поддержка отклонений - подстановочные знаки Java - делает его излишне сложным.
Насколько я знаю, в C# 3 нет ничего похожего на явные, ограниченные подстановочные знаки, например, вы не можете объявить метод PriceBatch, который принимает коллекцию Asset или любого подкласса Asset (void PriceBatch(Collection<? extends Asset> assets)
на Яве?).
Кто-нибудь знает, почему подстановочные знаки и границы не были добавлены в C#? Были ли эти функции намеренно упущены, чтобы упростить язык, или они просто еще не успели реализовать?
РЕДАКТИРОВАТЬ: Святой дым, комментарии от самого Эрика Липперта! Прочитав его проницательные комментарии Пола, я понимаю, что по крайней мере верхние границы поддерживаются и что приведенный выше пример может быть переведен в C# как:
void PriceBatch<T>(ICollection<T> assets) where T : Asset
Нижние границы, с другой стороны, явно не поддерживаются, как говорит Эрик во втором комментарии, например, вероятно, нет способа напрямую перевести этот (несколько надуманный) код 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# имеет верхние, но не нижние границы?
3 ответа
Сложный вопрос.
Сначала давайте рассмотрим ваш фундаментальный вопрос: "Почему это незаконно в C#?"
class C<T> where T : Mammal {} // legal
class D<T> where Giraffe : T {} // illegal
То есть ограничение общего типа может сказать "T должен быть любым ссылочным типом, который может быть назначен переменной типа Mammal", но не "T должен быть любым ссылочным типом, переменной которого может быть назначен жираф". Почему разница?
Я не знаю. Это было задолго до моего пребывания в команде C#. Тривиальный ответ: "потому что CLR не поддерживает его", но команда, которая разработала дженерики C#, была той же самой командой, которая разрабатывала дженерики CLR, так что это на самом деле не слишком много объяснений.
Я предполагаю, что, как всегда, для поддержки какой-либо функции необходимо разработать, внедрить, протестировать, задокументировать и отправить ее клиентам; никто никогда не делал ничего из этого для этой функции, и поэтому это не на языке. Я не вижу большой, убедительной выгоды от предлагаемой функции; Сложные функции без особых преимуществ, как правило, здесь.
Однако это предположение. В следующий раз мне доведется пообщаться с ребятами, которые работали над дженериками - они живут в Англии, так что, к сожалению, они совсем не похожи на меня, я спрошу.
Что касается вашего конкретного примера, я думаю, что Павел прав. Вам не нужны ограничения нижней границы, чтобы это работало в 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 имели ограниченную функциональность, подобную этой, поскольку это относится к делегатам и определенным типам массивов. Что касается делегатов, то допускаются делегаты, которые принимают базовые типы параметров. Что касается типов массивов, я думаю, что это действительно, если не будет вовлечен бокс. То есть массив Customer может быть регистром для массива объекта. Тем не менее, массив целых не может быть приведен к массиву объектов.
.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.
Я не в состоянии ответить на настоящий вопрос, хотя!