Не очень занимательный C#
Я как-то не особо лезу со своими комментариями к другим людям, а уж тем более к постам на хабре. Но вчера вот листал RSS-ленту и увидел интригующее название поста – "Занимательный C#. Пять примров для кофе-брейка".
Итить, думаю, дай-ка зайду, посмотрю, что да как.
И вот первая загадка – что выдаст следующий код: using System;
public struct SDummy : IDisposable
{
private bool _dispose;
public void Dispose() => _dispose = true;
public bool GetDispose() => _dispose;
private static void Main(string[] args)
{
var d = new SDummy();
using (d)
{
Console.WriteLine(d.GetDispose());
}
Console.WriteLine(d.GetDispose());
}
}
Ну, думаю, ок. Странно начинать с изменяемых структур и особенностей блока using, ну, ничего.
Отрыл объяснение, а в нем говорится, что причина странного поведения в упаковке, дескать. Компилятор зовет Dispose метод через каст: ((IDisposable)myStruct).Dispose(), ну а каст, как известно – это упаковка.
Вот те на, подумалось мне. Мало того, что черти дают достаточно невменяемые и не практичные загадки, так еще и ответы у них неверные.
Я добавил комментарий, после чего началось небольшое обсуждение. Дескать, сам Эрик "уже давно в фейсбуке работает" Липперт писал, что упаковка в блоке using быть должна и компилятор нарушает спеку и все такое... (хотя сегодня это и не так).
Меня смутило две вещи: то, как авторы загадки пытались выкрутиться и найти оправдание своей ошибке. Ну, и главное, что они полезли в дебри, не выяснив, что же в этих дебрях происходит. Да не просто полезли, они этим дебрям еще и учат.
Что в этом плохого?
Плохо то, что структуры в C# имеют две особенности – они являются "значениями", и могут располагаться напрямую в паяти контейнера (в стеке, регистрах и напрямую в других объектах).
Первое говорит о том, что в рантайме структуры по умолчанию копируются и то, что компилятор старается обеспечить семантику значения (т.е. неизменяемость) путем встраивания туда-сюда защитные копии (чтобы вызов метода или свойства, например, на неизменяемом поле ни в коем разе значение этого поля не поменял).
Второе же (место жизни структуры) может привести к упаковке т.е. перемещению экземпляра в кучу.
Два этих отличия очень важны и в голове они, по-хорошему, должны лежать на разных полочках. Ибо каждый из них достаточно сложен, может меняться и развиваться по мере развития языка, да и проявляются эти особенности по-разному.
Вот, например, семантика значения и защитные копии стали гораздо более распространенной бедой с выходом C# 7x с их модификаторами ‘in’ и возвратом по неизменяемой ссылке (readonly refs) (вот, например, много буков по этому поводу - The ‘in’-modifier and the readonly structs in C#). А са
А сама беда проявляется очень сильно с изменяемыми структурами, когда скрытая копия «спрячет» изменения состояния, поскольку произойти они могут на временной копии. Не столь больное последствие заключается в некоторой потери производительности за счет создания копии, что решается путем использования readonly структур.
Упаковка же происходит совсем в других местах, при кастах к объектам/интерфейсам и в более экзотических случаях, типа при вызове методов из System.Object или System.ValueType (когда, например, Equals/GetHashCode не переопределены). А проявляется она путем увеличения давления на сборку мусора, что может аукнуться за счет тормозов сборщика мусора.
Так это я все к чему: учить других – это хорошо. Это просто здорово! Но касаясь всяких закоулков языка и рантайма, хорошо бы понимать, что да как на самом деле происходит под капотом, да и желательно разбираться, почему это именно так.


