Причина в том, что он не использовался как ссылочный тип. Он был разработан, чтобы действовать как тип значения, за исключением одного конкретного. Давайте посмотрим, чем отличаются типы значений и ссылочные типы.
Основное различие между типом значения и ссылочным типом заключается в том, что тип значения является самодостаточным (переменная, содержащая фактическое значение), в то время как ссылочный тип относится к другому значению.
Отсюда вытекают и другие отличия. Тот факт, что мы можем напрямую использовать псевдонимы ссылочных типов (что имеет как хорошие, так и плохие последствия), исходит из этого. Также существуют различия в том, что означает равенство:
Тип значения имеет концепцию равенства на основе содержащегося в нем значения, которое при желании может быть переопределено (существуют логические ограничения на то, как это переопределение может происходить *). Ссылочный тип имеет концепцию идентичности, которая не имеет смысла с типами значений (поскольку они не могут иметь прямого псевдонима, поэтому два таких значения не могут быть идентичными), которые не могут быть переопределены, что также дает значение по умолчанию для его концепции равенства. По умолчанию ==
имеет дело с этим равенством на основе значений, когда дело касается типов значений †, но с идентичностью, когда дело касается ссылочных типов. Кроме того, даже когда ссылочному типу дается концепция равенства, основанная на значениях, и он используется для ==
, он никогда не теряет возможность сравниваться с другой ссылкой для идентификации.
Еще одно различие, связанное с этим, заключается в том, что ссылочные типы могут быть нулевыми - значение, которое относится к другому значению, допускает значение, которое не ссылается ни на какое значение, что и является нулевой ссылкой.
Кроме того, с этим связаны некоторые преимущества сохранения небольших типов значений, поскольку, будучи основанными на значении, они копируются по значению при передаче в функции.
Некоторые другие различия подразумеваются, но не влекут за собой этого. То, что часто бывает хорошей идеей сделать типы значений неизменяемыми, подразумевается, но не вытекает из основного различия, потому что, хотя есть преимущества, которые можно найти без учета вопросов реализации, есть также преимущества в том, чтобы сделать это со ссылочными типами (действительно, некоторые из них связаны с безопасностью с псевдонимы применяются более немедленно к ссылочным типам) и причины, по которым можно нарушить это руководство - так что это не жесткое и быстрое правило (с вложенными типами значений связанные риски настолько сильно снижаются, что у меня было бы немного сомнений в том, чтобы сделать изменяемый вложенный тип значения , хотя мой стиль сильно зависит от того, чтобы даже ссылочные типы были неизменными, когда это вообще возможно).
Некоторые дополнительные различия между типами значений и ссылочными типами, возможно, связаны с деталями реализации. То, что тип значения в локальной переменной имеет значение, хранящееся в стеке, было аргументировано как деталь реализации; вероятно, довольно очевидный, если в вашей реализации есть стек, и, безусловно, важный в некоторых случаях, но не основной для определения. Его также часто преувеличивают (для начала, ссылочный тип в локальной переменной также имеет ссылку в стеке, во-вторых, много раз значение типа значения сохраняется в куче).
С этим связаны некоторые дополнительные преимущества в том, что типы значений являются небольшими.
Теперь Nullable<T>
- это тип, который ведет себя как тип значения всеми способами, описанными выше, за исключением того, что он может принимать нулевое значение. Возможно, вопрос о хранении локальных значений в стеке не так уж и важен (это скорее деталь реализации, чем что-либо еще), но остальное зависит от того, как оно определяется.
Nullable<T>
определяется как
struct Nullable<T>
{
private bool hasValue;
internal T value;
/* methods and properties I won't go into here */
}
Большая часть реализации с этого момента очевидна. Требуется некоторая особая обработка, позволяющая присвоить ему значение null - обрабатываемое так, как если бы было присвоено default(Nullable<T>)
- и некоторая особая обработка при упаковке, а затем следует остальное (включая то, что его можно сравнить на равенство с null).
Если бы Nullable<T>
был ссылочным типом, тогда нам потребовалась бы особая обработка, чтобы все остальное могло произойти, а также особая обработка функций, позволяющих .NET помочь разработчику (например, нам потребовалась бы особая обработка, чтобы сделать это спускаются с ValueType
). Я даже не уверен, возможно ли это.
* Существуют некоторые ограничения на то, как нам разрешено переопределять равенство. Комбинируя эти правила с теми, которые используются в значениях по умолчанию, обычно мы можем позволить, чтобы два значения считались равными, которые по умолчанию считались бы неравными, но редко имеет смысл рассматривать два неравных значения, которые по умолчанию считались бы равными. Исключением является случай, когда структура содержит только типы-значения, но указанные типы-значения переопределяют равенство. Это результат оптимизации и обычно считается ошибкой, а не намеренно.
† Исключение составляют типы с плавающей запятой. Из-за определения типов-значений в стандарте CLI double.NaN.Equals(double.NaN)
и float.NaN.Equals(float.NaN)
возвращают true
. Но из-за определения NaN в ISO 60559, float.NaN == float.NaN
и double.NaN == double.NaN
оба возвращают false.
person
Jon Hanna
schedule
28.11.2010
Nullable<T>
- совершенно безумный тип, и пытаться рассуждать об этом логически - глупо. Закрой глаза, закрой уши и кричи ла ла ла ла ла Я не слышу тебя ла ла ла ла, пока он не уйдет, вот мой совет. - person Brian   schedule 28.11.2010