Следует ли предпочесть общие ограничения использованию интерфейсов в качестве типов параметров?

Рассмотрим эту тривиальную функцию:

public static bool IsPositive(IComparable<int> value)
{
    return value.CompareTo(0) > 0;
}

Теперь, если я передам этому методу int, он будет помещен в коробку. Не лучше ли было бы поэтому определить описанный выше метод следующим образом?

public static bool IsPositive<T>(T value) where T : IComparable<int>
{
    return value.CompareTo(0) > 0;
}

Таким образом, используя универсальное ограничение, я могу добиться точно такой же функциональности, как и в приведенном выше коде, с дополнительным преимуществом, заключающимся в том, что не требуется упаковка (поскольку вызов IsPositive<int> принимает параметр типа int).

Приведенный выше пример кода совершенно бесполезен. Но мой более широкий вопрос таков: не будет ли всегда определять методы последним способом (с использованием общего ограничения, а не с параметром некоторого типа интерфейса), чтобы избежать потенциального упаковка типов значений?

Я подозреваю, что ответ, скорее всего, будет «да, но для этого требуется больше печатать, и во многих случаях встреча с типом значения будет очень маловероятной, например, когда метод принимает некоторые IEnumerable<T>». Но мне интересно, есть ли большая разница между этими подходами, которая ускользает от меня в данный момент.


person Dan Tao    schedule 19.08.2010    source источник
comment
Разве бокс/специализация не касается только общих типов, а не функций?   -  person Dario    schedule 20.08.2010
comment
@Дарио: я не уверен, о чем ты спрашиваешь. Если тип значения передается методу, который принимает параметр типа интерфейса, это значение помещается в рамку. Вот почему внутри метода можно установить локальный параметр равным null. С другой стороны, при универсальном подходе локальный параметр внутри IsPositive<T> имеет тип T, который может быть типом значения или ссылочным типом. Имеет ли это смысл?   -  person Dan Tao    schedule 20.08.2010
comment
@Dan - В общем случае типы значений все еще необходимо упаковывать. IL допускает унифицированный способ использования типа с инструкцией constrained, но при компиляции в машинный код ничем не отличается от предыдущего метода упаковки — ничего не выиграешь.   -  person Mark H    schedule 20.08.2010
comment
@Mark H: Если то, что вы говорите, правда, то это явно самая веская причина из всех. Но ты уверен? Вы можете продемонстрировать это?   -  person Dan Tao    schedule 20.08.2010
comment
Возможно, нет, я буду сравнивать два :/   -  person Mark H    schedule 20.08.2010
comment
Хорошо, я ошибаюсь. Общий примерно на 10% быстрее. (Время более 100 000 00 итераций).   -  person Mark H    schedule 20.08.2010
comment
@Mark H: Что случилось с теми ссылками, которые вы разместили в pastebin? Я собирался освежить свой ИЖ и взглянуть на них!   -  person Dan Tao    schedule 20.08.2010
comment
@Dan: я удалил их, потому что у них не было всех деталей, необходимых для определения того, был ли какой-либо бокс (необходимо будет выполнить ряд вызовов и скопировать их). Оказывается, я ошибся. В любом случае получите вывод сами, просто поставьте точку останова в методе, который хотите захватить, и когда он сломается, введите disasm в командном окне Visual Studio.   -  person Mark H    schedule 20.08.2010
comment
@Mark H: я был удивлен, услышав, что универсальная версия содержит тип значения. Я снова и снова слышал (и из вполне авторитетных источников, хотя конкретный пример ускользает от меня в данный момент), что в .NET это не так, хотя и в Java. На самом деле, насколько я понимаю, это одна из самых гордых возможностей .NET.   -  person Dan Tao    schedule 20.08.2010
comment
Я считаю, что первую сигнатуру немного легче читать, поэтому я бы использовал ее, если только не столкнулся бы с узким местом в производительности, и в этот момент я бы протестировал обе в профилировщике (на самом деле я бы даже не подумал использовать ограничение для скорости скорость выполнения, но если бы я додумался сделать это).   -  person Brian    schedule 21.08.2010


Ответы (3)


Одна из проблем связана с тем, что общее ограничение на самом деле не является частью подписи. Если у вас есть ...

static T Method<T>(T value) where T : ICompareable<int>

... и ...

static T Method<T>(T value) where T : IEnumerable<int>

... у компилятора не было бы способа узнать, что есть что.

И вызвать Эрика Липперта...

person Matthew Whited    schedule 19.08.2010
comment
Я все еще жду того дня, когда смогу сказать where T.Parse(string) - person Matthew Whited; 20.08.2010
comment
А, очень хороший момент. Хорошо, я знал, что должно быть по крайней мере что-то, чего мне не приходит в голову. Но я жду больше ответов (потому что я уверен, что есть еще что-то, что я не принял во внимание). - person Dan Tao; 20.08.2010
comment
@ Мэтью Уайтд: я в замешательстве. where T.Parse(string) кажется довольно плохой идеей для ограничения. Или вы ждете, пока он появится в вопросе? - person Brian; 21.08.2010
comment
@Brian, идея заключалась бы в том, чтобы разрешить ограничение общего параметра на основе статического метода. Затем вы можете использовать метод (например, .Parse(...)) для типов, основанных на вашем методе. Это позволит вам писать универсальные адаптеры, не требующие отражения. - person Matthew Whited; 21.08.2010
comment
Кстати, сейчас эта идея потребует изменений в CLR, таких как что-то похожее на виртуальную таблицу поиска для статических методов. В настоящее время вызовы статических методов определяются во время компиляции и, как таковые, не могут использоваться со статическими методами (вы можете попытаться передать код IL, но это вызовет исключение во время выполнения, поскольку не сможет разрешить вызов метода.) - person Matthew Whited; 21.08.2010
comment
@Matthew Whited: Ограничение статическим методом кажется мне пугающим. Я мог представить, что такие статические методы могут иметь побочные эффекты, добавляя в код странные пути выполнения. - person Brian; 22.08.2010
comment
@ Брайан, это могло бы ... но то же самое могло бы быть и во всем остальном, что мы делаем. Но, как я указал прямо сейчас, это даже не имеет значения, потому что вы не можете сделать это изначально в CLR (то же самое касается множественного наследования). интерфейс определяет необходимые статические методы (например, IParse) или сигнатуры конструктора. Также было бы неплохо, если бы у нас было больше перегрузок операторов и если бы они могли быть виртуальными и нативными. (Сейчас перегрузки операторов — это действительно хаки, которые перенаправляются к методу через компилятор.) - person Matthew Whited; 22.08.2010
comment
Черт возьми, было бы довольно убедительно (хотя я думаю, что это довольно сложно) разрешить сигнатуры методов варьироваться в зависимости от возврата метода (в VB.Net/C# из-за того, что он поддерживается CLR и CIL). - person Matthew Whited; 22.08.2010

Была некоторая путаница в комментариях к вопросу о том, вызывает ли вызов метода упаковку после передачи аргумента.

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

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

http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.constrained.aspx

person Eric Lippert    schedule 20.08.2010

Другая проблема может заключаться в том, что универсальное ограничение относится к параметрам универсального типа параметризованного типа, например

static bool AreAllTheSame<T>(IEnumerable<T> something)
  where T : IEquatable<T>

Не всегда возможно преобразовать параметр универсального типа таким образом, если только вы не введете параметр второго типа следующим образом:

static bool AreAllTheSame<S, T>(S something)
  where S : IEnumerable<T>
  where T : IEquatable<T>

Это просто не выглядит правильно.

person Ruben    schedule 19.08.2010
comment
Но это совершенно нормально (за исключением того, что я думаю, что вам нужно изменить свой код, чтобы читать where S : IEnumerable<T> where T : IEquatable<T> - два ограничения where, а не одно с запятой). Вы говорите, что это не вариант, потому что это выглядит неправильно? - person Dan Tao; 20.08.2010
comment
@Dan: я исправил это. Причина, по которой я говорю, что это выглядит неправильно, заключается в том, что вам нужен дополнительный параметр типа только для того, чтобы успокоить механизм ограничений. Если вы посмотрите на сигнатуру и намерение метода, то увидите, что один из двух параметров совершенно лишний. Кроме того, таким образом T почти скрыт от потребителя API, хотя это (ИМХО) наиболее важный параметр типа. - person Ruben; 20.08.2010