Автор: Sergey Teplyakov

Я как-то не особо лезу со своими комментариями к другим людям, а уж тем более к постам на хабре. Но вчера вот листал 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 не переопределены). А проявляется она путем увеличения давления на сборку мусора, что может аукнуться за счет тормозов сборщика мусора.

Так это я все к чему: учить других это хорошо. Это просто здорово! Но касаясь всяких закоулков языка и рантайма, хорошо бы понимать, что, да как на самом деле происходит под капотом, да и желательно разбираться, почему происходит все именно так, а не иначе.

Помогла статья? Оцените её!
5 из 5. Общее количество голосов - 1
 

You have no rights to post comments

Дмитрий Крикунов

Публикую статьи, обучающие курсы и новости по программированию: алгоритмам, языкам (С++, Java), параллельному программированию, паттернам и библиотекам (Qt, boost).