Когда использовать структуру? - PullRequest
1307 голосов
/ 06 февраля 2009

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

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

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

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

Ответы [ 28 ]

16 голосов
/ 06 февраля 2009

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

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

15 голосов
/ 06 февраля 2009

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

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

.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 этих типов.

10 голосов
/ 20 июля 2017

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

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.

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

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
10 голосов
/ 24 июля 2017

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

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

P.S. Есть еще один тест для прохождения структуры / класса через стек вызовов https://stackoverflow.com/a/47864451/506147

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;
        }
    }
10 голосов
/ 25 июля 2012

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

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

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

9 голосов
/ 06 февраля 2009

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

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

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

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

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

8 голосов
/ 23 мая 2014

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

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

8 голосов
/ 30 августа 2013

Мое правило:

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

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

5 голосов
/ 28 марта 2016

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...