2010-11-24 3 views
22

Мне было интересно, почему Nullable<T> - тип значения, если он предназначен для имитации поведения ссылочных типов? Я понимаю такие вещи, как давление в ГК, но я не уверен, что если мы хотим, чтобы int действовал как ссылка, мы, вероятно, все в порядке со всеми последствиями наличия реального ссылочного типа. Я не вижу причин, почему Nullable<T> - это не только версия в коробке T struct.Почему Nullable <T> является структурой?

Как тип значения:

  1. он все еще нуждается в штучной упаковке и без упаковки, и многое другое, бокс должен быть немного иначе, чем с «нормальными» структурами (для лечения нуль-значной Nullable как реальное null)
  2. это нужно относиться по-разному при проверке нулевой (сделано просто в Equals, никакой реальной проблемы)
  3. оно изменчиво, нарушая правила, Структуры должны быть неизменны (ок, это логически неизменны)
  4. он должен иметь специальное ограничение, чтобы запретить рекурсию как Nullable<Nullable<T>>

Не делает Nullable<T> ссылочный тип решения, что вопросы?

перефразировать и обновление:

Я изменил мой список причин немного, но мой общий вопрос остается открытым:

Как будет ссылаться на тип Nullable<T> быть хуже, чем текущая реализация типа значения? Это только давление в ГК и «небольшое, неизменное» правило? Он по-прежнему чувствует себя странно для меня ...

+3

Реальный вопрос, является: почему не родовым Может , для всех типов T. – 2010-11-24 22:29:30

+3

@pst F # имеет возможность типа, но семантика не будет работать в C# без значительного сдвига от «Всех рефы могут быть nulls "to" Только для ссылок, которые могут быть указаны, может быть null ". В противном случае, какой смысл иметь тип Maybe? – CodexArcanum 2010-11-24 22:32:06

+1

Это очень сложный вопрос, вы должны пойти и прочитать соответствующие разделы CIL Standard.Nullable имеет отдельное положение в системе типов, и к нему применяется большая специальная обработка в CLR. Боюсь, что ответ не так прост, как вы могли бы пожелать. – 2010-11-27 16:13:45

ответ

13

Причина в том, что он был не предназначен для того, чтобы действовать как ссылочный тип. Он был разработан так, чтобы действовать как тип значения, за исключением только одного конкретного. Давайте рассмотрим несколько способов различения типов значений и ссылочных типов.

Основное отличие между значением и ссылочным типом состоит в том, что тип значения является автономным (переменная, содержащая фактическое значение), тогда как ссылочный тип относится к к другому значению.

Некоторые другие отличия связаны с этим. Из этого вытекает тот факт, что мы можем напрямую ссылаться на ссылочные типы (которые имеют как хорошие, так и плохие эффекты). Так же существуют различия в том, что означает равенство:

Тип значения имеет концепцию равенства на основе содержащегося значения, которое может быть опционально переопределено (существуют логические ограничения на то, как это переопределение может случиться *). Тип ссылки имеет концепцию идентичности, которая не имеет смысла в типах значений (поскольку они не могут быть напрямую сглажены, поэтому два таких значения не могут быть идентичными), которые нельзя переопределить, что также дает по умолчанию его концепцию равенства. По умолчанию == имеет дело с этим равенством, основанным на значении, когда речь заходит о типах значений †, но с идентичностью, когда речь идет о ссылочных типах. Кроме того, даже если ссылочному типу дается концепция равенства, основанная на значении, и она используется для ==, она никогда не теряет способность сравниваться с другой ссылкой для идентификации.

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

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

Некоторые другие различия подразумеваются, но не связаны с этим.То, что часто является хорошей идеей сделать неизменяемым тип ценности неизменным, но не вызвано основной разницей, потому что, хотя есть преимущества, которые можно найти без рассмотрения вопросов реализации, есть также преимущества при этом со ссылочными типами (действительно, некоторые из них касаются безопасности с псевдонимы применяются более немедленно к ссылочным типам) и причины, по которым можно нарушить это руководство - так что это не жесткое и быстрое правило (с вложенными типами значений такие риски настолько сильно уменьшены, что у меня было бы несколько сомнений при создании типа вложенного значения mutable , хотя мой стиль сильно опирается на то, чтобы сделать даже ссылочные типы неизменными, когда они вообще практичны).

Некоторые дополнительные различия между типами значений и ссылочными типами являются, возможно, деталями реализации. То, что тип значения в локальной переменной имеет значение, хранящееся в стеке, аргументировалось как деталь реализации; вероятно, довольно очевидный, если ваша реализация имеет стек и, безусловно, важна в некоторых случаях, но не является основной для определения. Он также часто завышается (для начала ссылочный тип в локальной переменной также имеет ссылку в стеке, для другого существует много раз, когда значение типа значения сохраняется в куче).

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


Теперь Nullable<T> это тип, который ведет себя как тип значения во всех описанных выше способов, за исключением того, что он может принимать нулевое значение. Возможно, вопрос о локальных значениях, хранящихся в стеке, не так важен (более подробно о реализации, чем что-либо еще), но остальное связано с тем, как оно определено.

Nullable<T> определяется как

struct Nullable<T> 
{ 
    private bool hasValue; 
    internal T value; 
    /* methods and properties I won't go into here */ 
} 

Большая часть реализации с этой точки очевидна. Требуется некоторая специальная обработка, которая должна быть присвоена нулевому значению - обрабатывается как default(Nullable<T>) - и в случае бонуса используется специальная обработка, а затем следующая запись (в том числе, что ее можно сравнить для равенства с нулем).

Если Nullable<T> был ссылочным типом, мы должны иметь специальную обработку, позволяющую всем остальным встречаться, а также специальную обработку функций в том, как .NET помогает разработчику (например, нам понадобится специальный чтобы сделать его снижением от ValueType). Я даже не уверен, что это будет возможно.

* Есть некоторые ограничения на то, как нам разрешено переопределять равенство. Объединяя эти правила с теми, которые используются в стандартах по умолчанию, тогда обычно мы можем разрешить считать два значения равными, которые по умолчанию считались бы неравными, но редко имеет смысл рассматривать два значения неравными, что значение по умолчанию будет считаться равным. Исключение - это случай, когда структура содержит только значения-типы, но где указанные типы значений переопределяют равенство. Это результат оптимизации и обычно считается ошибкой, а не дизайном.

† Исключением являются типы с плавающей точкой. Из-за определения типов значений в стандарте CLI double.NaN.Equals(double.NaN) и float.NaN.Equals(float.NaN) return true. Но из-за определения NaN в ISO 60559, float.NaN == float.NaN и double.NaN == double.NaN оба возвращают false.

8

Edited обратиться обновленный вопрос ...

Вы можете боксировать и распаковывать объекты, если вы хотите использовать на структуру в качестве ссылки.

Однако тип Nullable<> в основном позволяет повысить любой тип значения с дополнительным флагом состояния, который сообщает, должно ли оно использоваться как null, или если значение «действительно».

Так адресовать ваши вопросы:

  1. Это является преимуществом при использовании в коллекции, или из-за различную семантику (копирование вместо ссылок)

  2. Нет это не имеет. CLR действительно уважает это при боксе и распаковке, так что вы на самом деле никогда не вставляете экземпляр Nullable<>. Бокс a Nullable<>, который «имеет» значение не будет возвращать ссылку null, а распаковка делает обратное.

  3. Nope.

  4. Опять же, это не тот случай. На самом деле общие ограничения для структуры не позволяют использовать NULL-структуры. Это имеет смысл благодаря специальному поведению бокса/распаковки.Поэтому, если у вас есть where T: struct, чтобы ограничить общий тип, типы с нулевым значением будут запрещены. Так как это ограничение определено и для типа Nullable<T>, вы не можете вложить их, без какого-либо специального лечения, чтобы предотвратить это.

Почему бы не использовать ссылки? Я уже упомянул о важных семантических различиях. Но помимо этого ссылочные типы используют гораздо больше пространства памяти: каждая ссылка, особенно в 64-разрядных средах, использует не только память кучи для экземпляра, но также память для ссылки, информацию о типе экземпляра, блокирующие биты и т. Д. Таким образом, помимо семантики и различий в производительности (косвенное обращение через ссылку), вы в конечном итоге используете несколько единиц памяти, используемых для самой сущности для большинства общих объектов. И GC получает больше объектов для обработки, что сделает общую производительность по сравнению с структурами еще хуже.

6

Не изменен; проверить снова.

Бокс также отличается; пустые «ящики» равны нулю.

Но; он мал (чуть больше T), неизменен и инкапсулирует только структуры - идеалы как структуры. Возможно, что более важно, если T действительно является «значением», то и T? логическое «значение».

Смежные вопросы