Структуры могут содержать поля ссылочных типов


Могут ли структуры содержать поля ссылочных типов? И если они могут, разве это плохая практика?

6 46

6 ответов:

Да, они могут. Это хорошая идея? Ну, это зависит от ситуации. Лично я редко создаю свои собственные структуры в первую очередь... Я бы отнесся к любой новой пользовательской структуре с определенной долей скептицизма. Я не говорю, что это всегда неправильный вариант, просто ему нужен более четкий аргумент, чем класс.

Однако было бы плохой идеей для структуры иметь ссылку на изменяемый объект... в противном случае вы можете иметь два значения, которые выглядят независимы, но не являются:
MyValueType foo = ...;
MyValueType bar = foo; // Value type, hence copy...

foo.List.Add("x");
// Eek, bar's list has now changed too!
Изменчивые структуры-это зло. Неизменяемые структуры со ссылками на изменяемые типы являются скрытым злом по-разному.

Конечно, и это не плохая практика, чтобы сделать это.

struct Example {
  public readonly string Field1;
}

Только чтение не является необходимым, но это хорошая практика, чтобы сделать структуру неизменяемой.

Да, это возможно, и да, это обычно плохая практика.

Если вы посмотрите на саму платформу .NET framework, то увидите, что практически все структуры содержат только примитивные типы значений.

Да, они могут.

Это зависит.

Многие придерживаются позиции, что структура должна быть неизменной, и в этом случае наличие ссылки на объект может означать, что это не так.

Но это зависит от ситуации.

Причина, по которой вы не можете иметь изменяемые структуры, заключается в поведении ссылочных типов. Прочитайте эту статью: http://www.yoda.arachsys.com/csharp/parameters.html

Когда у вас есть структура, содержащая объект (все, что не является примитивом, например int или double), и вы копируете экземпляр структуры, объект внутри не копируется "глубоко", потому что это просто ссылка (указатель) на место в памяти, содержащее фактический класс. Поэтому, если вы копируете изменяемую структуру, это содержит экземпляры класса, копия будет ссылаться на те же экземпляры, что и оригинал (следовательно, список бара изменяется выше).

Если вам абсолютно необходимо, чтобы структура была изменяемой, сделайте все экземпляры класса внутри только для чтения, или - это плохая практика - постарайтесь гарантировать, что вы никогда не сделаете копию структуры.

С тех пор, как это стало понижением, я пытаюсь немного переписать, чтобы увидеть, может ли это стать более ясным. Этот вопрос стар, но хорош! Недавно я также наткнулся на несколько ссылок, которые подробно описывают это.

Я пытаюсь добавить, что если вы делаете объявление ссылочных полей, вы должны быть в состоянии рассуждать вне вашего собственного блока: когда кто-то использует вашу структуру. Конкретный пункт, который я добавил, был действительно только об объявлении поля только для чтения структуры; но в этом случае поля, которые вы имеете в своей структуре, могут изменить свои результаты; и об этом трудно рассуждать.

Я наткнулся на эту ссылку, где программист объявил класс , содержащий readonly struct Поле. Поле в его классе-это struct - - - это LinkedList<T>.Enumerator - и оно сломалось, потому что поле readonly - - - его собственные методы класса получают копию структуры перечислителя, и состояние копируется, а не динамическое.

Но , если вы продолжите и исправите его код просто удалив readonly из поля структуры (которое работает); и затем, однако , Если вы затем решите сделать свой собственный класс A struct, теперь еще раз, потребитель вашей структуры не может использовать его в качестве поля только для чтения, иначе они в свою очередь будут укушены той же проблемой. (И если это кажется надуманным, потому что у вас не будет перечислителя только для чтения, вы на самом деле могли бы, если он поддерживает сброс!)

Так что если это не самый яркий пример, то я хочу сказать, что вы можете рассуждать о своей собственной реализации, но если вы являетесь структурой, вам нужно также рассуждать о потребителях, которые копируют вашу ценность и то, что они получат.

Пример, который я нашел, связан ниже.

Его класс не является struct, но содержит поле m_Enumerator (и программист предположительно знает, что оно является A struct).

Оказывается, что методы этого класса получают копию этого значения и не работают. --- Ты можешь на самом деле изучите Этот блок очень тщательно, чтобы понять это.

Вы можете исправить это, сделав поле не readonly --- что уже указывает на путаницу. Но вы можете также исправить это, объявив поле как Тип interface - - - IEnumerator<int>.

но , Если вы делаете исправить это, оставив поле объявлено как struct и объявить его не readonly, и затем определите свой класс как struct, затем теперь Если кто-то объявляет экземпляр вашего struct как поле readonly в некотором классе, снова они проигрывают!

Е. Г.:

public class Program
{
    private struct EnumeratorWrapper : IEnumerator<int>
    {
        // Fails always --- the local methods read the readonly struct and get a copy
        //private readonly LinkedList<int>.Enumerator m_Enumerator;

        // Fixes one: --- locally, methods no longer get a copy;
        // BUT if a consumer of THIS struct makes a readonly field, then again they will
        // always get a copy of THIS, AND this contains a copy of this struct field!
        private LinkedList<int>.Enumerator m_Enumerator;

        // Fixes both!!
        // Because this is not a value type, even a consumer of THIS struct making a
        // readonly copy, always reads the memory pointer and not a value
        //private IEnumerator<int> m_Enumerator;


        public EnumeratorWrapper(LinkedList<int> linkedList) 
            => m_Enumerator = linkedList.GetEnumerator();


        public int Current
            => m_Enumerator.Current;

        object System.Collections.IEnumerator.Current
            => Current;

        public bool MoveNext()
            => m_Enumerator.MoveNext();

        public void Reset()
            => ((System.Collections.IEnumerator) m_Enumerator).Reset();

        public void Dispose()
            => m_Enumerator.Dispose();
    }


    private readonly LinkedList<int> l = new LinkedList<int>();
    private readonly EnumeratorWrapper e;


    public Program()
    {
        for (int i = 0; i < 10; ++i) {
            l.AddLast(i);
        }
        e = new EnumeratorWrapper(l);
    }


    public static void Main()
    {
        Program p = new Program();

        // This works --- a local copy every time
        EnumeratorWrapper e = new EnumeratorWrapper(p.l);
        while (e.MoveNext()) {
            Console.WriteLine(e.Current);
        }

        // This fails if the struct cannot support living in a readonly field
        while (p.e.MoveNext()) {
            Console.WriteLine(p.e.Current);
        }
        Console.ReadKey();
    }
}

Если вы объявите struct С полем interface, вы не будете знать, что у вас там, но все же вы можете на самом деле рассуждать больше о том, что вы получаете, когда просто ссылаетесь на него! Это очень интересно; но только потому, что язык позволяет так много свобод с struct: вам нужно начать с чего-то очень простого; и добавить только о чем конкретно можно рассуждать!

Еще один момент заключается в том, что ссылка также говорит, что вы должны определить значения по умолчанию как разумные; что не возможно с ссылочным полем! Если вы случайно вызовете конструктор по умолчанию-всегда возможно с struct - - - то вы получите нулевую ссылку.

Еще одно последнее замечание. Многие люди защищают изменчивые структуры и большие изменчивые структуры. Но если вы всмотритесь, вы обычно обнаружите, что они просто определение этих объектов таким образом, чтобы они могли конечно рассуждать о поведении, и структуры не просачиваются в области, где эти инварианты могли бы измениться.

... Слишком многие люди начинают объяснять структуры как " просто класс, но ... x, y, z, 1, 2, альфа, бета диско". Это должно быть объяснено как пара значений только для чтения; точка; кроме того, теперь, когда вы что-то знаете, вы можете начать рассуждать о добавлении чего-то!

Пример, к которому я пришел, - это здесь:

Https://www.red-gate.com/simple-talk/blogs/why-enumerator-structs-are-a-really-bad-idea/