Когда использовать структуру?

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

Я столкнулся с этими правилами здесь:

  • Структура должна представлять одно значение.
  • Структура должна иметь объем памяти менее 16 байт.
  • Структура не должна быть изменена после создания.

Эти правила работают? Что означает семантически структура?

33 ответа

Источник, на который ссылается OP, заслуживает доверия... но как насчет Microsoft - какова позиция по использованию struct? Я искал дополнительное обучение у Microsoft, и вот что я нашел:

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

Не определяйте структуру, если тип не имеет всех следующих характеристик:

  1. Логически представляет одно значение, похожее на примитивные типы (целое, двойное и т. Д.).
  2. Размер экземпляра меньше 16 байтов.
  3. Это неизменно.
  4. Это не должно быть упаковано часто.

Microsoft постоянно нарушает эти правила

Хорошо, № 2 и № 3 в любом случае. Наш любимый словарь имеет 2 внутренних структуры:

[StructLayout(LayoutKind.Sequential)]  // default for structs
private struct Entry  //<Tkey, TValue>
{
    //  View code at *Reference Source
}

[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Enumerator : 
    IEnumerator<KeyValuePair<TKey, TValue>>, IDisposable, 
    IDictionaryEnumerator, IEnumerator
{
    //  View code at *Reference Source
}

* Справочный источник

Источник 'JonnyCantCode.com' получил 3 из 4 - вполне простительно, так как #4, вероятно, не будет проблемой. Если вы обнаружите, что занимаетесь структурой, переосмыслите свою архитектуру.

Давайте посмотрим, почему Microsoft будет использовать эти структуры:

  1. Каждая структура, Entry а также Enumerator, представляют отдельные значения.
  2. скорость
  3. Entry никогда не передается как параметр вне класса Dictionary. Дальнейшее исследование показывает, что для удовлетворения реализации IEnumerable, словарь использует Enumerator Структура, которую он копирует каждый раз при запросе перечислителя... имеет смысл.
  4. Внутренний в классе словаря. Enumerator является общедоступным, поскольку Dictionary является перечислимым и должен иметь равный доступ к реализации интерфейса IEnumerator, например, к получателю IEnumerator.

Обновление. Кроме того, следует понимать, что когда структура реализует интерфейс - как это делает Enumerator - и приводится к этому реализованному типу, структура становится ссылочным типом и перемещается в кучу. Внутренний по отношению к классу Dictionary, Enumerator по- прежнему является типом значения. Однако, как только вызов метода GetEnumerator()ссылочный тип IEnumerator возвращается

Здесь мы не видим никаких попыток или доказательств необходимости сохранять неизменяемыми структуры или поддерживать размер экземпляра не более 16 байт:

  1. Ничто в вышеприведенных структурах не заявлено readonly - не неизменный
  2. Размер этой структуры может быть больше 16 байт
  3. Entry имеет неопределенный срок службы (от Add(), чтобы Remove(), Clear()или сборка мусора);

И... 4. Обе структуры хранят TKey и TValue, которые, как мы все знаем, вполне могут быть ссылочными типами (дополнительная информация о бонусе)

Несмотря на хэшированные ключи, словари работают быстро, потому что создание структуры быстрее, чем ссылочного типа. Здесь у меня есть Dictionary<int, int> который хранит 300000 случайных целых чисел с последовательно увеличенными ключами.

Вместимость: 312874
MemSize: 2660827 байт
Завершено Изменение размера: 5 мс
Общее время заполнения: 889мс

Capacity: количество элементов, доступных до изменения размера внутреннего массива.

MemSize: определяется путем сериализации словаря в MemoryStream и получения длины в байтах (достаточно точной для наших целей).

Завершено Изменение размера: время, необходимое для изменения размера внутреннего массива с 150862 элементов до 312874 элементов. Когда вы понимаете, что каждый элемент последовательно копируется через Array.CopyTo()Это не так уж и плохо.

Общее время для заполнения: по общему признанию перекошено из-за регистрации и OnResize событие, которое я добавил к источнику; Тем не менее, по-прежнему впечатляет заполнить целых 300000 при изменении размера в 15 раз во время операции Просто из любопытства, сколько будет общего времени, чтобы заполнить, если бы я уже знал емкость? 13 мс

Итак, что если Entry был класс? Будут ли эти времена или показатели действительно сильно отличаться?

Вместимость: 312874
MemSize: 2660827 байт
Завершено Изменение размера: 26 мс
Общее время заполнения: 964мс

Очевидно, что большая разница заключается в изменении размера. Есть ли разница, если словарь инициализируется с Capacity? Не достаточно, чтобы беспокоиться о... 12 мс.

Что происходит, потому что Entry является структурой, она не требует инициализации, как ссылочный тип. Это и красота, и проклятие типа значения. Для того, чтобы использовать Entry в качестве ссылочного типа мне пришлось вставить следующий код:

/*
 *  Added to satisfy initialization of entry elements --
 *  this is where the extra time is spent resizing the Entry array
 * **/
for (int i = 0 ; i < prime ; i++)
{
    destinationArray[i] = new Entry( );
}
/*  *********************************************** */  

Причина, по которой я должен был инициализировать каждый элемент массива Entry в качестве справочного типа можно найти на MSDN: Конструкция структуры. Короче:

Не предоставляйте конструктор по умолчанию для структуры.

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

Некоторые компиляторы, такие как компилятор C#, не позволяют структурам иметь конструкторы по умолчанию.

Это на самом деле довольно просто, и мы заимствуем из трех законов робототехники Азимова:

  1. Структура должна быть безопасной для использования
  2. Структура должна эффективно выполнять свою функцию, если это не нарушит правило № 1
  3. Структура должна оставаться неповрежденной во время ее использования, если ее уничтожение не требуется для удовлетворения правила № 1

...что мы уберем от этого: короче говоря, будем нести ответственность за использование типов значений. Они бывают быстрыми и эффективными, но имеют способность вызывать множество неожиданных действий, если их неправильно обслуживать (например, непреднамеренные копии)

Всякий раз, когда вам не нужен полиморфизм, вам нужна семантика значений и вы хотите избежать выделения кучи и связанных с этим издержек сборки мусора. Однако предостережение заключается в том, что структуры (произвольно большие) обходятся дороже, чем ссылки на классы (обычно одно машинное слово), поэтому классы могут оказаться быстрее на практике.

Я не согласен с правилами, приведенными в оригинальном посте. Вот мои правила:

1) Вы используете структуры для производительности при хранении в массивах. (см. также, когда составляется ответ?)

2) Они нужны вам при передаче кода структурированных данных в / из C / C++

3) Не используйте структуры, если они вам не нужны:

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

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

редактировать

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

Если вам нужна ссылочная семантика, вам нужен класс, а не структура.

В дополнение к ответу "это значение", один конкретный сценарий использования структур - это когда вы знаете, что у вас есть набор данных, вызывающий проблемы со сборкой мусора, и у вас много объектов. Например, большой список / массив экземпляров Person. Естественной метафорой здесь является класс, но если у вас есть большое количество долгоживущих экземпляров Person, они могут в конечном итоге засорить GEN-2 и вызвать остановку GC. Если сценарий этого требует, один из возможных подходов заключается в использовании массива (а не списка) структур Person, т.е. Person[], Теперь, вместо миллионов объектов в GEN-2, у вас есть отдельный кусок в LOH (я предполагаю, что здесь нет строк и т. Д. - то есть чистое значение без каких-либо ссылок). Это имеет очень небольшое влияние GC.

Работать с этими данными неудобно, поскольку данные, вероятно, слишком велики для структуры, и вы не хотите постоянно копировать жирные значения. Однако доступ к нему напрямую в массиве не копирует структуру - она ​​на месте (в отличие от индексатора списка, который копирует). Это означает большую работу с индексами:

int index = ...
int id = peopleArray[index].Id;

Обратите внимание, что поддержание неизменности самих значений поможет здесь. Для более сложной логики используйте метод с параметром by-ref:

void Foo(ref Person person) {...}
...
Foo(ref peopleArray[index]);

Опять же, это на месте - мы не скопировали значение.

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

Из спецификации языка C#:

1.7 Структуры

Как и классы, структуры - это структуры данных, которые могут содержать элементы данных и функции, но в отличие от классов, структуры являются типами значений и не требуют выделения кучи. Переменная типа структуры непосредственно хранит данные структуры, тогда как переменная типа класса хранит ссылку на динамически размещенный объект. Структурные типы не поддерживают пользовательское наследование, и все структурные типы неявно наследуются от объекта типа.

Структуры особенно полезны для небольших структур данных, которые имеют семантику значений. Комплексные числа, точки в системе координат или пары ключ-значение в словаре - все это хорошие примеры структур. Использование структур, а не классов для небольших структур данных, может существенно повлиять на количество выделяемых памяти приложением. Например, следующая программа создает и инициализирует массив из 100 точек. С помощью Point, реализованного в виде класса, создается 101 отдельный объект - один для массива и один для 100 элементов.

class Point
{
   public int x, y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}

class Test
{
   static void Main() {
      Point[] points = new Point[100];
      for (int i = 0; i < 100; i++) points[i] = new Point(i, i);
   }
}

Альтернатива - сделать Point структурой.

struct Point
{
   public int x, y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}

Теперь создается только один объект - один для массива - и экземпляры Point хранятся в виде строки в массиве.

Конструкторы Struct вызываются оператором new, но это не означает, что память выделяется. Вместо того, чтобы динамически размещать объект и возвращать ссылку на него, конструктор структуры просто возвращает само значение структуры (обычно во временном месте в стеке), и это значение затем копируется по мере необходимости.

В случае классов две переменные могут ссылаться на один и тот же объект и, таким образом, операции с одной переменной могут влиять на объект, на который ссылается другая переменная. В структурах каждая из переменных имеет свою собственную копию данных, и операции с одним из них не могут повлиять на другое. Например, вывод, полученный следующим фрагментом кода, зависит от того, является ли Point классом или структурой.

Point a = new Point(10, 10);
Point b = a;
a.x = 20;
Console.WriteLine(b.x);

Если Point является классом, результат равен 20, потому что a и b ссылаются на один и тот же объект. Если Point является структурой, выходное значение равно 10, поскольку при присваивании a to b создается копия значения, и последующее присвоение ax не влияет на эту копию.

В предыдущем примере освещены два ограничения структур. Во-первых, копирование всей структуры обычно менее эффективно, чем копирование ссылки на объект, поэтому присвоение и передача параметров-значений могут быть более дорогими для структур, чем для ссылочных типов. Во-вторых, за исключением параметров ref и out, невозможно создавать ссылки на структуры, что исключает их использование в ряде ситуаций.

Вот основное правило.

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

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

Exmaples

public struct MyPoint 
{
    public int X; // Value Type
    public int Y; // Value Type
}

public class MyPointWithName 
{
    public int X; // Value Type
    public int Y; // Value Type
    public string Name; // Reference Type
}

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

Первое: взаимодействие сценариев или когда вам нужно указать расположение памяти

Второе: когда данные в любом случае имеют почти такой же размер, что и ссылочный указатель.

Я сделал небольшой тест с BenchmarkDotNet, чтобы лучше понять преимущества "struct" в числах. Я тестирую цикл по массиву (или списку) структур (или классов). Создание этих массивов или списков выходит за рамки теста - ясно, что более тяжелый "класс" будет использовать больше памяти и будет включать GC.

Таким образом, вывод таков: будьте осторожны с LINQ и боксом / распаковкой скрытых структур, а использование структур для микрооптимизации строго придерживается массивов.

PS Есть еще один тест для прохождения struct / class через стек вызовов /questions/9693525/v-chem-raznitsa-mezhdu-strukturoj-i-klassom-vnet/9693530#9693530

BenchmarkDotNet=v0.10.8, OS=Windows 10 Redstone 2 (10.0.15063)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233542 Hz, Resolution=309.2584 ns, Timer=TSC
  [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Clr    : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Core   : .NET Core 4.6.25211.01, 64bit RyuJIT


          Method |  Job | Runtime |      Mean |     Error |    StdDev |       Min |       Max |    Median | Rank |  Gen 0 | Allocated |
---------------- |----- |-------- |----------:|----------:|----------:|----------:|----------:|----------:|-----:|-------:|----------:|
   TestListClass |  Clr |     Clr |  5.599 us | 0.0408 us | 0.0382 us |  5.561 us |  5.689 us |  5.583 us |    3 |      - |       0 B |
  TestArrayClass |  Clr |     Clr |  2.024 us | 0.0102 us | 0.0096 us |  2.011 us |  2.043 us |  2.022 us |    2 |      - |       0 B |
  TestListStruct |  Clr |     Clr |  8.427 us | 0.1983 us | 0.2204 us |  8.101 us |  9.007 us |  8.374 us |    5 |      - |       0 B |
 TestArrayStruct |  Clr |     Clr |  1.539 us | 0.0295 us | 0.0276 us |  1.502 us |  1.577 us |  1.537 us |    1 |      - |       0 B |
   TestLinqClass |  Clr |     Clr | 13.117 us | 0.1007 us | 0.0892 us | 13.007 us | 13.301 us | 13.089 us |    7 | 0.0153 |      80 B |
  TestLinqStruct |  Clr |     Clr | 28.676 us | 0.1837 us | 0.1534 us | 28.441 us | 28.957 us | 28.660 us |    9 |      - |      96 B |
   TestListClass | Core |    Core |  5.747 us | 0.1147 us | 0.1275 us |  5.567 us |  5.945 us |  5.756 us |    4 |      - |       0 B |
  TestArrayClass | Core |    Core |  2.023 us | 0.0299 us | 0.0279 us |  1.990 us |  2.069 us |  2.013 us |    2 |      - |       0 B |
  TestListStruct | Core |    Core |  8.753 us | 0.1659 us | 0.1910 us |  8.498 us |  9.110 us |  8.670 us |    6 |      - |       0 B |
 TestArrayStruct | Core |    Core |  1.552 us | 0.0307 us | 0.0377 us |  1.496 us |  1.618 us |  1.552 us |    1 |      - |       0 B |
   TestLinqClass | Core |    Core | 14.286 us | 0.2430 us | 0.2273 us | 13.956 us | 14.678 us | 14.313 us |    8 | 0.0153 |      72 B |
  TestLinqStruct | Core |    Core | 30.121 us | 0.5941 us | 0.5835 us | 28.928 us | 30.909 us | 30.153 us |   10 |      - |      88 B |

Код:

[RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
    [ClrJob, CoreJob]
    [HtmlExporter, MarkdownExporter]
    [MemoryDiagnoser]
    public class BenchmarkRef
    {
        public class C1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        public struct S1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        List<C1> testListClass = new List<C1>();
        List<S1> testListStruct = new List<S1>();
        C1[] testArrayClass;
        S1[] testArrayStruct;
        public BenchmarkRef()
        {
            for(int i=0;i<1000;i++)
            {
                testListClass.Add(new C1  { Text1= i.ToString(), Text2=null, Text3= i.ToString() });
                testListStruct.Add(new S1 { Text1 = i.ToString(), Text2 = null, Text3 = i.ToString() });
            }
            testArrayClass = testListClass.ToArray();
            testArrayStruct = testListStruct.ToArray();
        }

        [Benchmark]
        public int TestListClass()
        {
            var x = 0;
            foreach(var i in testListClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayClass()
        {
            var x = 0;
            foreach (var i in testArrayClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestListStruct()
        {
            var x = 0;
            foreach (var i in testListStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayStruct()
        {
            var x = 0;
            foreach (var i in testArrayStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestLinqClass()
        {
            var x = testListClass.Select(i=> i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }

        [Benchmark]
        public int TestLinqStruct()
        {
            var x = testListStruct.Select(i => i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }
    }

Вам необходимо использовать "struct" в ситуациях, когда вы хотите явно указать макет памяти с помощью StructLayoutAttribute - обычно для PInvoke.

Редактировать: Комментарий указывает, что вы можете использовать класс или структуру со StructLayoutAttribute, и это, безусловно, верно. На практике вы обычно используете struct - она ​​распределяется в стеке, а не в куче, что имеет смысл, если вы просто передаете аргумент в вызов неуправляемого метода.

Я использую структуры для упаковки или распаковки любого двоичного формата связи. Это включает чтение или запись на диск, списки вершин DirectX, сетевые протоколы или работу с зашифрованными / сжатыми данными.

Три указанных вами руководства не были полезны для меня в этом контексте. Когда мне нужно выписать четыреста байтов материала в определенном порядке, я определю структуру размером в четыреста байтов и заполню ее любыми несвязанными значениями, которые она должна иметь, и я собираюсь установить его любым способом, который имеет больше всего смысла в то время. (Хорошо, четыреста байтов было бы довольно странно, но когда я писал для жизни файлы Excel, я имел дело со структурами размером до сорока байтов всего, потому что вот каковы некоторые из записей BIFF.)

За исключением типов значений, которые используются непосредственно средой выполнения и различными другими для целей PInvoke, значения типов типов следует использовать только в 2 сценариях.

  1. Когда вам нужно скопировать семантику.
  2. Когда вам нужна автоматическая инициализация, обычно в массивах этих типов.

МИФ № 1: СТРУКТЫ - ЛЕГКИЕ КЛАССЫ

Этот миф бывает разных форм. Некоторые люди считают, что типы значений не могут или не должны иметь методы или другое существенное поведение - их следует использовать как простые типы передачи данных, просто с открытыми полями или простыми свойствами. Тип DateTime является хорошим контрпримером к этому: имеет смысл для него быть типом значения с точки зрения фундаментальной единицы, такой как число или символ, и также имеет смысл иметь возможность выполнять вычисления на основе его ценность. Если посмотреть на вещи с другой стороны, типы передачи данных часто должны быть ссылочными типами, так как решение должно основываться на желаемом значении или семантике ссылочного типа, а не на простоте типа. Другие люди считают, что типы значений "легче", чем ссылочные типы с точки зрения производительности. Правда в том, что в некоторых случаях типы значений более производительны - им не требуется сборка мусора, если они не упакованы, не имеют накладных расходов на идентификацию типов и, например, не требуют разыменования. Но в других отношениях ссылочные типы более производительны - передача параметров, присвоение значений переменным, возвращаемые значения и аналогичные операции требуют только 4 или 8 байтов для копирования (в зависимости от того, используете ли вы 32-разрядный или 64-разрядный CLR) вместо того, чтобы копировать все данные. Представьте, что ArrayList был каким-то "чистым" типом значения, и передача выражения ArrayList методу включала бы копирование всех его данных! Практически во всех случаях производительность не зависит от такого решения в любом случае. Узкие места почти никогда не бывают такими, как вы думаете, и прежде чем принимать решение о проектировании, основанное на производительности, вы должны оценить различные варианты. Стоит отметить, что сочетание двух убеждений тоже не работает. Не имеет значения, сколько методов имеет тип (будь то класс или структура) - память, взятая для каждого экземпляра, не затрагивается. (С точки зрения памяти, затрачиваемой на сам код, существует определенная стоимость, но это происходит один раз, а не для каждого экземпляра.)

МИФ № 2: СПРАВОЧНЫЕ ТИПЫ ЖИТЬ НА КАРТЕ; ЦЕННЫЕ ТИПЫ ЖИТЬ НА СТЕКЕ

Это часто вызвано ленью со стороны человека, повторяющего это. Первая часть верна - экземпляр ссылочного типа всегда создается в куче. Это вторая часть, которая вызывает проблемы. Как я уже отмечал, значение переменной живет везде, где она объявлена, поэтому, если у вас есть класс с переменной экземпляра типа int, значение этой переменной для любого данного объекта всегда будет там, где находятся остальные данные для объекта - в кучу. В стеке живут только локальные переменные (переменные, объявленные в методах) и параметры метода. В C# 2 и более поздних версиях даже некоторые локальные переменные на самом деле не живут в стеке, как вы увидите, когда мы рассмотрим анонимные методы в главе 5. СООТВЕТСТВУЮТ ЛИ ЭТИ КОНЦЕПЦИИ СЕЙЧАС? Можно утверждать, что если вы пишете управляемый код, вы должны позволить среде выполнения беспокоиться о том, как лучше всего использовать память. Действительно, спецификация языка не дает никаких гарантий относительно того, что и где живет; будущая среда выполнения может создать некоторые объекты в стеке, если знает, что это сойдет с рук, или компилятор C# может сгенерировать код, который вообще не использует стек. Следующий миф обычно является просто вопросом терминологии.

Миф № 3: объекты передаются по ссылке в C# по умолчанию

Это, наверное, самый распространенный миф. Опять же, люди, которые делают это заявление часто (хотя и не всегда), знают, как на самом деле ведет себя C#, но они не знают, что на самом деле означает "передача по ссылке". К сожалению, это сбивает с толку людей, которые знают, что это значит. Формальное определение передачи по ссылке является относительно сложным, включающим l-значения и аналогичную компьютерную терминологию, но важно то, что если вы передаете переменную по ссылке, вызываемый вами метод может изменить значение переменной вызывающей стороны изменив значение параметра. Теперь помните, что значением переменной ссылочного типа является ссылка, а не сам объект. Вы можете изменить содержимое объекта, на который ссылается параметр, без передачи самого параметра по ссылке. Например, следующий метод изменяет содержимое рассматриваемого объекта StringBuilder, но выражение вызывающей стороны все равно будет ссылаться на тот же объект, что и раньше:

void AppendHello(StringBuilder builder)
{
    builder.Append("hello");
}

Когда этот метод вызывается, значение параметра (ссылка на StringBuilder) передается по значению. Если бы вы изменили значение переменной построителя в методе - например, с помощью оператора builder = null; - это изменение не было бы замечено вызывающей стороной, вопреки мифу. Интересно отметить, что не только бит "по ссылке" мифа неточен, но и бит "объекты переданы". Сами объекты никогда не передаются ни по ссылке, ни по значению. Когда задействован ссылочный тип, либо переменная передается по ссылке, либо значение аргумента (ссылка) передается по значению. Помимо всего прочего, это отвечает на вопрос о том, что происходит, когда null используется в качестве аргумента по значению - если объекты передавались, это вызывало бы проблемы, поскольку не было бы объекта для передачи! Вместо этого нулевая ссылка передается по значению так же, как и любая другая ссылка. Если это быстрое объяснение оставило вас в замешательстве, вы можете взглянуть на мою статью "Передача параметров в C#" ( http://mng.bz/otVt), в которой гораздо больше подробностей. Эти мифы не единственные вокруг. Бокс и распаковка приходят из-за их значительного недопонимания, которое я постараюсь прояснить позже.

Ссылка: C# в глубине 3-е издание Джон Скит

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

public struct IntStruct {
    public int Value {get; set;}
}

Исключение следующих результатов приводит к 5 экземплярам структуры, хранящейся в памяти:

var struct1 = new IntStruct() { Value = 0 }; // original
var struct2 = struct1;  // A copy is made
var struct3 = struct2;  // A copy is made
var struct4 = struct3;  // A copy is made
var struct5 = struct4;  // A copy is made

// NOTE: A "copy" will occur when you pass a struct into a method parameter.
// To avoid the "copy", use the ref keyword.

// Although structs are designed to use less system resources
// than classes.  If used incorrectly, they could use significantly more.

Класс является ссылочным типом. Когда вы назначаете класс новой переменной, переменная содержит ссылку на исходный объект класса.

public class IntClass {
    public int Value {get; set;}
}

Исключение следующих результатов приводит только к одному экземпляру объекта класса в памяти.

var class1 = new IntClass() { Value = 0 };
var class2 = class1;  // A reference is made to class1
var class3 = class2;  // A reference is made to class1
var class4 = class3;  // A reference is made to class1
var class5 = class4;  // A reference is made to class1  

Struct s может увеличить вероятность ошибки кода. Если объект значения обрабатывается как изменяемый ссылочный объект, разработчик может удивиться, когда внесенные изменения будут неожиданно потеряны.

var struct1 = new IntStruct() { Value = 0 };
var struct2 = struct1;
struct2.Value = 1;
// At this point, a developer may be surprised when 
// struct1.Value is 0 and not 1

.NET поддерживает value types а также reference types (в Java вы можете определять только ссылочные типы). Экземпляры reference types распределяются в управляемой куче и удаляются, когда на них нет выдающихся ссылок. Экземпляры value typesс другой стороны, выделяются в stackи, следовательно, выделенная память восстанавливается, как только заканчивается их объем. И, конечно же, value types получить по значению, и reference types по ссылке. Все примитивные типы данных C#, за исключением System.String, являются типами значений.

Когда использовать структуру над классом,

В C# structs являются value typesЗанятия reference types, Вы можете создавать типы значений в C#, используя enum Ключевое слово и struct ключевое слово. Используя value type вместо reference type приведет к уменьшению количества объектов в управляемой куче, что приведет к меньшей нагрузке на сборщик мусора (GC), менее частым циклам GC и, следовательно, к повышению производительности. Тем не мение, value types есть и свои минусы тоже. Обходя большой struct это определенно дороже, чем передача ссылки, это одна очевидная проблема. Другая проблема - это накладные расходы, связанные с boxing/unboxing, Если вам интересно, что boxing/unboxing значит, перейдите по этим ссылкам для хорошего объяснения boxing а также unboxing, Помимо производительности, бывают случаи, когда вам просто нужно, чтобы типы имели семантику значений, что было бы очень сложно (или уродливо) реализовать, если reference types все, что у вас есть. Вы должны использовать value types только когда вам нужна копирование семантики или автоматическая инициализация, обычно в arrays из этих типов.

Структурные типы в C# или других языках.net обычно используются для хранения вещей, которые должны вести себя как группы значений фиксированного размера. Полезный аспект типов структуры состоит в том, что поля экземпляра типа структуры могут быть изменены путем изменения места хранения, в котором он хранится, и никак иначе. Можно кодировать структуру таким образом, что единственный способ изменить любое поле - это создать совершенно новый экземпляр, а затем использовать присвоение структуры, чтобы изменить все поля цели, перезаписав их значениями из нового экземпляра, но если структура не предоставляет средств для создания экземпляра, где ее поля имеют значения не по умолчанию, все ее поля будут изменяемыми, если и если сама структура хранится в изменяемой папке.

Обратите внимание, что можно спроектировать тип структуры так, чтобы он по существу вел себя как тип класса, если структура содержит частное поле типа класса и перенаправляет своих собственных членов на объект обернутого класса. Например, PersonCollection может предложить недвижимость SortedByName а также SortedByIdоба из которых содержат "неизменную" ссылку на PersonCollection (установить в своем конструкторе) и реализовать GetEnumerator позвонив либо creator.GetNameSortedEnumerator или же creator.GetIdSortedEnumerator, Такие структуры будут вести себя как ссылка на PersonCollectionкроме того, что их GetEnumerator методы будут связаны с различными методами в PersonCollection, Можно также иметь структуру, обертывающую часть массива (например, можно определить ArrayRange<T> структура, которая будет содержать T[] называется ArrInt Offsetи int Length, с индексированным свойством, которое для индекса idx в диапазоне от 0 до Length-1, будет доступ Arr[idx+Offset]). К сожалению, если foo является экземпляром такой структуры, доступным только для чтения, текущие версии компилятора не допускают таких операций, как foo[3]+=4; потому что они не могут определить, будут ли такие операции пытаться записывать в поля foo,

Также возможно спроектировать структуру, которая будет вести себя как тип значения, который содержит коллекцию переменного размера (которая будет копироваться всякий раз, когда структура), но единственный способ выполнить эту работу - убедиться, что ни один объект, которому Структура содержит ссылку, которая будет когда-либо подвергаться воздействию всего, что может ее изменить. Например, можно иметь массивоподобную структуру, которая содержит приватный массив, и чей индексированный метод "put" создает новый массив, содержимое которого похоже на оригинал, за исключением одного измененного элемента. К сожалению, может быть довольно сложно заставить такие структуры работать эффективно. Хотя бывают случаи, когда семантика структуры может быть удобной (например, возможность передавать массивоподобную коллекцию в подпрограмму, когда вызывающая сторона и вызываемая сторона знают, что внешний код не будет изменять коллекцию, может быть лучше, чем требовать как вызывающую, так и требование защищенного копирования любых данных, которые они дают), требование, чтобы ссылки на классы указывали на объекты, которые никогда не будут видоизменяться, часто является довольно серьезным ограничением.

Нет, я не совсем согласен с правилами. Они являются хорошими рекомендациями для оценки производительности и стандартизации, но не в свете возможностей.

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

В этом случае я использую классы для представления объектов реального мира в их большей форме, я использую структуры для представления более мелких объектов, которые имеют более точное использование. То, как вы это сказали, "более сплоченное целое". Ключевое слово является связным. Классы будут более объектно-ориентированными элементами, в то время как структуры могут иметь некоторые из этих характеристик, хотя и в меньшем масштабе. ИМО.

Я часто их использую в тегах Treeview и Listview, где очень быстро можно получить доступ к общим статическим атрибутам. Я всегда изо всех сил пытался получить эту информацию другим способом. Например, в моих приложениях базы данных я использую Treeview, где у меня есть таблицы, SP, функции или любые другие объекты. Я создаю и заполняю свою структуру, помещаю ее в тег, вытаскиваю, получаю данные выбора и так далее. Я бы не стал делать это с классом!

Я стараюсь держать их маленькими, использую их в единичных ситуациях и не допускаю их изменения. Целесообразно осознавать память, распределение и производительность. И тестирование так необходимо.

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

Классы и структуры (Руководство по программированию в C#)

Мое правило

1, всегда используйте класс;

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

Я имел дело с именованным каналом Windows Communication Foundation [WCF] и заметил, что имеет смысл использовать Structs, чтобы гарантировать, что обмен данными имеет тип значения вместо ссылочного типа.

Вкратце, используйте struct, если:

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

2- свойства и поля в вашем объекте являются типом значения, и они не так велики.

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

Я думаю, что хорошее первое приближение - "никогда".

Я думаю, что хорошее второе приближение - "никогда".

Если вы отчаянно нуждаетесь в совершенстве, подумайте о них, но всегда измеряйте.

Структура C# является легкой альтернативой классу. Он может делать почти то же самое, что и класс, но дешевле использовать структуру, а не класс. Причина этого немного техническая, но в итоге новые экземпляры класса помещаются в кучу, а новые экземпляры структур помещаются в стек. Кроме того, вы не имеете дело со ссылками на структуры, как с классами, но вместо этого вы работаете непосредственно с экземпляром структуры. Это также означает, что когда вы передаете структуру в функцию, это по значению, а не как ссылка. Подробнее об этом рассказывается в главе о параметрах функций.

Таким образом, вы должны использовать структуры, когда вы хотите представить более простые структуры данных, особенно если вы знаете, что будете создавать их экземпляры. Существует множество примеров в.NET Framework, где Microsoft использует структуры вместо классов, например, структуру Point, Rectangle и Color.

Ниже приведены правила, определенные на веб-сайте Microsoft:

✔️ РАССМАТРИВАЙТЕ определение структуры вместо класса, если экземпляры типа небольшие и обычно недолговечны или обычно встраиваются в другие объекты.

❌ ИЗБЕГАЙТЕ определения структуры, если тип не имеет всех следующих характеристик:

Он логически представляет собой одно значение, подобное примитивным типам (int, double и т. Д.).

Он имеет размер экземпляра менее 16 байт.

Это непреложно.

Его не придется часто коробить.

для дальнейшего чтения

Struct может быть использован для повышения производительности сборки мусора. Хотя вам обычно не нужно беспокоиться о производительности GC, есть сценарии, где это может быть убийственно. Как большие кэши в приложениях с низкой задержкой. Смотрите этот пост для примера:

http://00sharp.wordpress.com/2013/07/03/a-case-for-the-struct/

Я редко использую структуру для вещей. Но это только я. Это зависит от того, нужен ли мне объект или нет.

Как указано в других ответах, я использую классы для объектов реального мира. У меня также есть мышление структур, которые используются для хранения небольших объемов данных.

Типы структуры или значения могут использоваться в следующих сценариях:

  1. Если вы хотите предотвратить сбор объекта при сборке мусора.
  2. Если это простой тип и никакая функция-член не изменяет свои поля экземпляра
  3. Если нет необходимости выводить из других типов или получать из других типов.

Вы можете узнать больше о типах значений и типах значений здесь по этой ссылке

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

Не используйте структуру, если значения ее полей по умолчанию не представляют разумное значение по умолчанию моделируемой концепции.

Например.

  • Цвет или Точка имеют смысл, даже если для всех их полей установлены значения по умолчанию. RGB 0,0,0 - идеальный цвет, как и (0,0) как точка в 2D.
  • Но Address или PersonName не имеют разумного значения по умолчанию. Я имею в виду, можете ли вы понять PersonName, у которого FirstName = null и LastName = null?

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

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

(Обычный отказ от ответственности: соображения производительности могут перекрыть этот совет. Если у вас есть проблемы с производительностью, всегда измеряйте, прежде чем принимать решение. Попробуйте BenchmarkDotNet, это потрясающе!)

✔️ УЧИТЫВАЙТЕ использование структуры

  1. Создайте объект или не нужно создавать объект (напрямую вы можете назначать значения, он создает объект)
  2. Need Speed ​​или улучшение производительности
  3. Нет необходимости в конструкторах и дестракторах (доступен статический подрядчик)
  4. Нет необходимости в наследовании классов, но интерфейсы приемлемы
  5. Работа с небольшими объектами рабочей нагрузки, если она станет высокой, возникнет проблема с памятью
  6. Вы не можете использовать значения по умолчанию для переменных.
  7. Struct также доступны методы, события, статические конструкторы, переменные и т. Д.
  8. Меньше нагрузки на сборщик мусора
  9. Нет необходимости в ссылочных типах, только тип значения (каждый раз, когда вы создаете новый объект)
  10. Нет неизменяемого объекта (строка является неизменяемым объектом, потому что любая операция не возвращает каждый раз новую строку без изменения оригинала)
Другие вопросы по тегам