Столкнулся на работе с интересной точкой зрения. В проекте все мало-мальские data transfer objects сделаны структурами. На вопрос, зачем так было сделано, получил ответ, что таким образом данные защищаются от изменений. С одной стороны, да, звучит, в принципе, логично, любые методы получают только копию и не в силах ничего подменить.
Но с другой стороны такое подход несет гораздо больше проблем, чем решает.
Во-первых, использование структур бьет и по производительности, и по памяти. Каждое обращение — это копирование, любой массив структур практически гарантированно попадет в Large Object Heap, что еще раз ударит и по производительности (из-за увеличенного времени в GC), и по памяти (фрагментированная куча — это очень плохо).
Во-вторых, использование структур черевато ошибками. Например:
На экран будет выведено, разумеется,
Но то, что это «разумеется» станет понятно только после пристального изучения кода, причем, только с IDE — понять, что Foo — это структура в любом другом просмоторщике практически нереально, особенно, если Foo определена в другом проекте. .NET разработчики же привыкли оперировать ссылками (о чем, кстати, красноречиво говорит баг в Решарпере — он предлагает заменить цикл 'for' на 'foreach', чего делать, оказывается, нельзя).
И в-третьих, на самом деле чувство «защищенности» обманчиво. Нередко в этих структурах встречаются массивы (даже если и структур), что делает эти «immutable» структуры вовсе не «immutable». Например:
После выполнения вы увидите «Other Name», что абсолютно логично и было бы даже очевидно, если бы Foo был не структурой, а классом. Но в случае структур понять, что разработчик хотел именно изменить значение в массиве, а не попался в ловушку псевдонеизменности, опять-таки, весьма и весьма не просто.
Выводы напрашиваются сами собой (разумеется, здравый смысл и частные случаи никто не отменял):
Но с другой стороны такое подход несет гораздо больше проблем, чем решает.
Во-первых, использование структур бьет и по производительности, и по памяти. Каждое обращение — это копирование, любой массив структур практически гарантированно попадет в 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 был не структурой, а классом. Но в случае структур понять, что разработчик хотел именно изменить значение в массиве, а не попался в ловушку псевдонеизменности, опять-таки, весьма и весьма не просто.
Выводы напрашиваются сами собой (разумеется, здравый смысл и частные случаи никто не отменял):
- Используйте структуры только там, где это действительно необходимо и оправдано (P/Invoke, например).
- Не используйте массивы в качестве возвращаемых типов — используете IEnumerable<T> или в крайнем случае IList<T> (но только как ReadOnlyCollection<T>).
- Если уж и используете структуры — делайте их поистинну immutable.