Когда тип значения содержит ссылочный тип?
Я понимаю, что решение использовать тип значения над ссылочным типом должно основываться на семантике, а не на производительности. Я не понимаю, почему типы значений могут легально содержать элементы ссылочного типа? Это связано с несколькими причинами:
Во-первых, мы не должны создавать конструкцию, требующую конструктора.
public struct MyStruct
{
public Person p;
// public Person p = new Person(); // error: cannot have instance field initializers in structs
MyStruct(Person p)
{
p = new Person();
}
}
Во-вторых, из-за семантики типа значения:
MyStruct someVariable;
someVariable.p.Age = 2; // NullReferenceException
Компилятор не позволяет мне инициализировать Person
в объявлении. Я должен переместить это в конструктор, полагаться на вызывающего абонента или ожидать NullReferenceException
. Ни одна из этих ситуаций не идеальна.
Есть ли в .NET Framework примеры ссылочных типов в типах значений? Когда мы должны это делать (если когда-либо)?
Ответы
Ответ 1
Экземпляры типа значения никогда не содержат экземпляров ссылочного типа. Объект с типичным типом находится где-то на управляемой куче, а объект с типизированным значением может содержать ссылку на объект. Такая ссылка имеет фиксированный размер. Это совершенно обычное дело для этого. например, каждый раз, когда вы используете строку внутри структуры.
Но да, вы не можете гарантировать инициализацию поля с типом ссылки в struct
, потому что вы не можете определить конструктор без параметров (и вы не можете гарантировать, что он когда-либо вызывается, если вы определяете его на языке, отличном от С#).
Вы говорите, что вы должны "не строить struct
, чтобы требовать конструктор". Я говорю иначе. Поскольку типы значений почти всегда должны быть неизменными, вы должны использовать конструктор (возможно, через factory для частного конструктора). В противном случае у него никогда не будет интересного содержимого.
Используйте конструктор. Конструктор в порядке.
Если вы не хотите передавать экземпляр Person
для инициализации p
, вы можете использовать ленивую инициализацию через свойство. (Потому что, очевидно, публичное поле p
предназначалось только для демонстрации, правда?)?
public struct MyStruct
{
public MyStruct(Person p)
{
this.p = p;
}
private Person p;
public Person Person
{
get
{
if (p == null)
{
p = new Person(…); // see comment below about struct immutability
}
return p;
}
}
// ^ in most other cases, this would be a typical use case for Lazy<T>;
// but due to structs' default constructor, we *always* need the null check.
}
Ответ 2
Существует два основных сценария использования для структуры, содержащей поле типа класса:
- Структура содержит возможно изменяемую ссылку на неизменяемый объект (`String`, безусловно, наиболее распространенный). Ссылка на неизменяемый объект будет вести себя как пересечение типа нулевых значений и типа нормального значения; он не имеет свойств "Value" и "HasValue" первого, но он будет иметь нуль как возможное (и значение по умолчанию). Обратите внимание: если доступ к полю осуществляется через свойство, это свойство может возвращать ненулевое значение по умолчанию, когда поле имеет значение NULL, но не должно изменять его.
- Структура содержит "неизменяемую" ссылку на возможно изменяемый объект и служит для обертывания объекта или его содержимого. `List.Enumerator`, вероятно, является наиболее распространенной структурой, использующей этот шаблон. Наличие структурных полей, претендующих на неизменность, является чем-то вроде изворотливой конструкции (*), но в некоторых контекстах это может получиться довольно хорошо. В большинстве случаев, когда этот шаблон применяется, поведение структуры будет по существу аналогично поведению класса, за исключением того, что производительность будет лучше (**).
(*) Оператор structVar = new structType(whatever);
создаст новый экземпляр structType
, передаст его конструктору, а затем mutate structVar
, скопировав все открытые и закрытые поля из этого нового экземпляра в structVar
; как только это будет сделано, новый экземпляр будет отброшен. Следовательно, все структурные поля изменяемы, даже если они "притворяются" иначе; притворяясь, что они являются неизменными, могут быть изворотливыми, если только не известно, что способ structVar = new structType(whatever);
фактически реализован никогда не будет представлять проблемы.
(**) В некоторых случаях структуры будут лучше работать; классы будут лучше работать в других. Как правило, так называемые "неизменяемые" структуры выбираются по классам в ситуациях, когда ожидается, что они будут лучше работать, и где угловые случаи, когда их семантика будет отличаться от классов, не будут создавать проблем.
Некоторым людям нравится притворяться, что структуры похожи на классы, но более эффективны и не любят использовать структуры таким образом, чтобы использовать тот факт, что они являются классами not. Такие люди, вероятно, были бы склонны использовать сценарий (2) выше. Сценарий №1 может быть очень полезен с изменяемыми структурами, особенно с такими типами, как String
, которые ведут себя как значения.
Ответ 3
Я хотел добавить к Marc ответ, но у меня было слишком много, чтобы сказать для комментария.
Если вы посмотрите на спецификации С#, то он говорит о конструкторах конструкций:
Конструкторы Struct вызываются с новым оператором, но это делает не означает, что память распределяется. Вместо динамического выделения объекта и возврата ссылки на него, структуры constructor просто возвращает значение struct непосредственно (обычно в временное местоположение в стеке), и это значение затем копируется как необходимо.
(Вы можете найти копию спецификации под
C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC#\Specifications\1033
)
Итак, конструктор структуры по сути отличается от конструктора класса.
В дополнение к этому, структуры, как ожидается, будут скопированы по значению, и таким образом:
С помощью структур каждая переменная имеет свою собственную копию данных и невозможно, чтобы операции на одном влияли на другие.
Каждый раз, когда я видел ссылочный тип в структуре, это была строка. Это работает, потому что строки неизменяемы. Я предполагаю, что ваш объект Person
не является неизменным и может вводить очень странные и суровые ошибки из-за расхождения с ожидаемым поведением структуры.
При этом ошибки, которые вы видите с конструктором вашей структуры, могут заключаться в том, что у вас есть общедоступное поле p
с тем же именем, что и ваш параметр p
, и не ссылайтесь на struct p
как на this.p
, или что вам не хватает ключевого слова struct
.