Как стать автором
Обновить

Еще одна причина, почему структуры — это плохо

Время на прочтение3 мин
Количество просмотров3.4K
Столкнулся на работе с интересной точкой зрения. В проекте все мало-мальские data transfer objects сделаны структурами. На вопрос, зачем так было сделано, получил ответ, что таким образом данные защищаются от изменений. С одной стороны, да, звучит, в принципе, логично, любые методы получают только копию и не в силах ничего подменить.

Но с другой стороны такое подход несет гораздо больше проблем, чем решает.

Во-первых, использование структур бьет и по производительности, и по памяти. Каждое обращение — это копирование, любой массив структур практически гарантированно попадет в Large Object Heap, что еще раз ударит и по производительности (из-за увеличенного времени в GC), и по памяти (фрагментированная куча — это очень плохо).

Во-вторых, использование структур черевато ошибками. Например:

class Program
{
  static void Main(string[] args)
  {
    Foo[] f = new[] {new Foo {Field = 5}};

    for (int i = 0; i < f.Length; i++)
    {
      Foo foo = f[i];
      foo.Field = 10;
      Console.WriteLine(foo.Field);
    }

    Console.WriteLine(f[0].Field);
  }
}
struct Foo
{
  public int Field;
}

* This source code was highlighted with Source Code Highlighter.


На экран будет выведено, разумеется,
10
5

Но то, что это «разумеется» станет понятно только после пристального изучения кода, причем, только с IDE — понять, что Foo — это структура в любом другом просмоторщике практически нереально, особенно, если Foo определена в другом проекте. .NET разработчики же привыкли оперировать ссылками (о чем, кстати, красноречиво говорит баг в Решарпере — он предлагает заменить цикл 'for' на 'foreach', чего делать, оказывается, нельзя).

И в-третьих, на самом деле чувство «защищенности» обманчиво. Нередко в этих структурах встречаются массивы (даже если и структур), что делает эти «immutable» структуры вовсе не «immutable». Например:
class Program
{
  static void Main(string[] args)
  {
    Foo f = new Foo
          {
            Field = 5,
            Bars = new[]
                  {
                    new Bar {Name = "My Name"}
                  }
          };

    BadFunction(f);
    Console.WriteLine(f.Bars[0].Name);
  }

  private static void BadFunction(Foo foo)
  {
    foo.Field = 10;
    foo.Bars[0] = new Bar {Name = "Other Name"};
  }
}
struct Foo
{
  public int Field;
  public Bar[] Bars;
}
struct Bar
{
  public string Name;
}

* This source code was highlighted with Source Code Highlighter.


После выполнения вы увидите «Other Name», что абсолютно логично и было бы даже очевидно, если бы Foo был не структурой, а классом. Но в случае структур понять, что разработчик хотел именно изменить значение в массиве, а не попался в ловушку псевдонеизменности, опять-таки, весьма и весьма не просто.

Выводы напрашиваются сами собой (разумеется, здравый смысл и частные случаи никто не отменял):
  1. Используйте структуры только там, где это действительно необходимо и оправдано (P/Invoke, например).
  2. Не используйте массивы в качестве возвращаемых типов — используете IEnumerable<T> или в крайнем случае IList<T> (но только как ReadOnlyCollection<T>).
  3. Если уж и используете структуры — делайте их поистинну immutable.
Теги:
Хабы:
Всего голосов 63: ↑38 и ↓25+13
Комментарии61

Публикации

Истории

Работа

.NET разработчик
53 вакансии

Ближайшие события

27 августа – 7 октября
Премия digital-кейсов «Проксима»
МоскваОнлайн
14 сентября
Конференция Practical ML Conf
МоскваОнлайн
19 сентября
CDI Conf 2024
Москва
20 – 22 сентября
BCI Hack Moscow
Москва
24 сентября
Конференция Fin.Bot 2024
МоскваОнлайн
25 сентября
Конференция Yandex Scale 2024
МоскваОнлайн
28 – 29 сентября
Конференция E-CODE
МоскваОнлайн
28 сентября – 5 октября
О! Хакатон
Онлайн
30 сентября – 1 октября
Конференция фронтенд-разработчиков FrontendConf 2024
МоскваОнлайн
3 – 18 октября
Kokoc Hackathon 2024
Онлайн