Причина в том, что это , а не , предназначенное для работы в качестве ссылочного типа. Он был разработан, чтобы действовать как тип значения, за исключением только одного конкретного. Давайте рассмотрим некоторые различия между типами значений и ссылочными типами.
Основное различие между значением и ссылочным типом состоит в том, что тип значения является автономным (переменная, содержащая фактическое значение), тогда как ссылочный тип ссылается на другое значение.
Некоторые другие различия связаны с этим. Отсюда тот факт, что мы можем использовать псевдонимы для ссылок непосредственно (что имеет как хорошие, так и плохие последствия). Так же как и различия в том, что означает равенство:
Тип значения имеет концепцию равенства, основанную на содержащемся значении, которое может быть необязательно переопределено (существуют логические ограничения того, как это переопределение может происходить *). У ссылочного типа есть концепция тождества, которая не имеет смысла для типов значений (поскольку они не могут иметь прямой псевдоним, поэтому два таких значения не могут быть идентичными), которые не могут быть переопределены, что также дает значение по умолчанию для его концепции равенства. По умолчанию ==
имеет дело с этим равенством на основе значений, когда речь идет о типах значений †, но с тождеством, когда речь идет о ссылочных типах. Кроме того, даже когда ссылочному типу дается основанная на значении концепция равенства и он используется для ==
, он никогда не теряет возможности сравниваться с другой ссылкой на идентичность.
Другим отличием является то, что ссылочные типы могут быть нулевыми - значение, ссылающееся на другое значение, допускает значение, которое не относится ни к какому значению, то есть к нулевой ссылке.
Кроме того, к этому относятся некоторые преимущества сохранения малых типов значений, поскольку, будучи основанными на значении, они копируются по значению при передаче в функции.
Некоторые другие различия подразумеваются, но не связаны с этим. То, что часто бывает полезно сделать типы значений неизменяемыми, подразумевается, но не влечет за собой принципиальное различие, потому что, хотя есть преимущества, которые можно найти без учета вопросов реализации, есть и преимущества в использовании со ссылочными типами (в действительности, некоторые, относящиеся к безопасности с псевдонимы применяются более немедленно к ссылочным типам) и причины, по которым можно нарушить это правило - так что это не сложное и быстрое правило (при использовании вложенных типов значений риски настолько сильно снижаются, что у меня не возникнет сомнений в том, что вложенный тип значения может быть изменяемым (хотя мой стиль сильно зависит от того, чтобы сделать даже ссылочные типы неизменными, когда это вообще возможно).
Некоторые дополнительные различия между типами значений и ссылочными типами, возможно, являются деталями реализации. То, что тип значения в локальной переменной имеет значение, хранящееся в стеке, обсуждалось как деталь реализации; вероятно, довольно очевидный, если у вашей реализации есть стек, и, конечно, важный в некоторых случаях, но не основной для определения. Он также часто преувеличивается (для начала, ссылочный тип в локальной переменной также имеет ссылку в стеке, для другой - много раз, когда значение типа значения сохраняется в куче).
К этому относятся некоторые дополнительные преимущества в типах значений, являющихся небольшими.
Теперь 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>)
- и некоторая специальная обработка в штучной упаковке, а затем следует остальное (включая то, что его можно сравнивать на равенство с нулем).
Если бы 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.