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