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

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

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;
}

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

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

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

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

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

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

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

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

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

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

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

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

Его класс не struct, но содержит поле m_Enumerator (и программист предположительно знает, что это 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/

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

Это зависит.

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

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

Другие вопросы по тегам