Подтвердить что ты не робот

Почему Nullable <T> является структурой?

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

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

  • он по-прежнему должен быть в коробке и распакованном, и многое другое, бокс должен быть немного иным, чем с "нормальными" структурами (для обработки нулевых значений с нулевым значением, например real null)
  • его нужно обрабатывать по-разному при проверке нулевого (сделано просто в Equals, никакой реальной проблемы)
  • он изменяет, нарушая правило, что структуры должны быть неизменными (нормально, он логически неизменен)
  • ему нужно специальное ограничение, чтобы запретить рекурсию, например Nullable<Nullable<T>>

Не делает ли Nullable<T> ссылочным типом решение проблем?

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

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

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

4b9b3361

Ответ 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.

Ответ 2

Отредактировано для ответа на обновленный вопрос...

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

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

Итак, чтобы ответить на ваши вопросы:

  • Это преимущество при использовании в коллекциях или из-за различной семантики (копирование вместо ссылки)

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

  • Нет.

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

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

Ответ 3

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

Бокс тоже отличается; пустые "ящики" до нуля.

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