Показаны сообщения с ярлыком C#. Показать все сообщения
Показаны сообщения с ярлыком C#. Показать все сообщения

вторник, 12 февраля 2019 г.

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

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

среда, 1 февраля 2017 г.

Исследуем new() ограничение в C#

В предыдущей заметке я спросил у многоуважаемой аудитории, что мне делать с англоязычными постами. Мнения разделились: часть аудитории согласились с публикацией здесь лишь анонсов, а другая часть посоветовала переводить. Я, правда, хотел бы переводить, но не уверен, что у меня хватит запала.

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

Ну а теперь, к теме сегодняшней публикации.

Я уже несколько раз затрагивал вопрос реализации одной довольно простой возможности языка C# - ограничения обобщений new(), что она, дескать, реализована через Activator.CreateInstance (да и то, не всегда;), подробности – в оригинале!).

Проблем у этого аспекта аж две (что и делает new() ограничение выдающейся дырявой абстракцией): низкая производительность и хитрость с обработкой исключений.

Так вот, у нас на проекте, активное использование new T() весьма быстро вылезло в профилировщике, было починено с весьма заметным приростом end-to-end времени исполнения. Там мы прикрутили простое решение на основе деревьев выражения и про это забыли.

А не так давно на ru.stackoverflow.com был задан вопрос по поводу кодогенерации и примеров ее применения, что дало дополнительную почву для размышлений на эту же тему. В результате были перекопаны следующие вещи, чтобы добиться эффективности кастомного активатора равных вызову делегата вида () => new CustomNode():

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

В результате работы над постом, была получена обобщенная фабрика, эффективность которой равна эффективности делегата, создающего конкретный экземпляр. Что, как мне кажется, весьма интересный результат;)

Понятное дело, что подробности – по ссылке: Dissecting the new() constraint in C#: a perfect example of a leaky abstraction.

З.Ы. Я надеюсь, что читать такое введение интереснее, чем просто увидеть ссылку.

З.Ы.Ы. Пожелания, предложения и все такое, всячески приветствуется.

четверг, 22 октября 2015 г.

Пишем простой анализатор с помощью Roslyn

С выходом новой версии студии (VS2015) у каждого из нас появилась возможность почувствовать себя причастным к разработке инструментов для разработчиков. Камрады из команд компиляторов C#/VB проделали отличную работу по «выставлению» внутренностей компилятора наружу, что позволяет теперь писать свои собственные анализаторы кода, затрачивая вполне разумные на это силы.

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

Для чего нужны свои собственные анализаторы?

Вопрос вполне разумный. Есть «реактивные мозги», есть DevExpress, есть же мелкомягкие товарищи из DevDiv-а, которые пилят инструменты для разработчиков. Зачем мне разбираться со всякими иммутабельными синтаксическими деревьями и control flow анализом? Это довольно весело, но разве этого достаточно, чтобы тратить на это свое ценное время?

Любой инструмент общего назначения рассчитан на решение наиболее типичных задач. Достаточно открыть список анализаторов Решарпера, чтобы понять, о чем идет речь. Такие анализаторы отлично справиться с поиском недостижимого кода или с предупреждением о неправильной реализации синглтона, но он не «подскажет» о правилах специфичных для вашего проекта и/или библиотеки.

Например, вы можете захотеть реагировать более жестко на некорректное логгирование исключений (детектить и «бить по пальцам», если вашему методу логирования передается ex.Message, а не ex.ToString()), или же это может быть кастомное правило, запрещающее использовать LINQ в определенных сборках во избежание потери производительности. Если в вашей команде есть правило или набор правил, которому должны следовать все члены команды, но которое нельзя выразить в виде правил FxCop/StyleCop. Все эти задачи отлично будут решаться с помощью самописных анализаторов.

среда, 23 сентября 2015 г.

Элегантная реализация «слабых событий»

Камрад @v2_matveev указал на прекрасную реализацию слабых событий в коде Roslyn-а.

Проблема стара как мир: подписка на события долгоживущего объекта со стороны короткоживущего продлевает время жизни последнего. Сложно? Может быть. Но столкнуться с проблемой довольно легко: если у вас есть синглтон с событиями, поздравляю, в вашем приложении есть эта проблема и связанные с ней утечки памяти. Поскольку событие содержит корневую ссылку на хендлер, то любой подписчик будет жить до тех пор, пока он не отпишется от события или пока поставщик событий не умрет (что в случае синглтона произойдет лишь при выгрузке домена или окончания работы приложения).

Решается эта проблема разными способами. Самый правильный способ – не использовать события в синглтонах. Второй способ – отписываться от событий вовремя. Третий способ – хипстерский, воспользоваться той или иной реализацией Weak Event Pattern-а, например, с помощью WeakEventManager или чего-то подобного. Но раз уж зашла речь за слыбые события, то их можно реализовать по разному и далее показана самая изящная реализация.

Вначале демонстрируем проблему: добавляем синглтон с событиями и короткоживущий объект, который на них подписывается:

вторник, 31 марта 2015 г.

Предусловия в конструкторах наследников

Существует одна типичная проблема с проверкой аргументов в конструкторах классов-наследников, которые должны передавать «куски» своих аргументов базовому классу. Простой пример этой проблемы выглядит так:

abstract class Base
{
   
private readonly int _length
;

   
protected Base(int length
)
    {
       
if (length <= 0) throw new ArgumentOutOfRangeException("length"
);

       
_length = length
;
    }
}

internal class Derived : Base
{
   
public Derived(string s
)
        :
base(s.Length
)
    {
       
// Проверка бесполезна!
        if (string.IsNullOrEmpty(s
)) throw new ArgumentNullException("s");
    }
}

Проблема в том, что конструктор базового класса вызывается до тела метода конструктора Derived (*). В результате, если конструктор наследника принимает составной объект и «выкусывает» часть, которая нужна конструктору базовому классу, то нормально проверить валидность составного объекта не получиться.

понедельник, 23 марта 2015 г.

Простой Syntax Highlighter на базе Roslyn

Roslyn – очень классная штука, с помощью которой можно не только анализаторы делать, но и много других интересных штук.

Вот, например, сколько потребуется усилий, чтобы сделать раскрашиватель синтаксиса, который будет печатать C#-файл в консоль и подсвечивать ключевые слова, строковые литералы и т.п вещи?

Вот как это можно сделать.

вторник, 10 марта 2015 г.

Анализатор исключений на базе Roslyn-а

Уже давно хотел поразбираться с анализаторами на основе Розлина. Тем более, что меня уже был опыт создания плагинов для Resharper-а (R# Contract Editor Extension), поэтому хотелось сравнить разные инфраструктуры и удобство использования. Есть идея переписать этот плагин с помощью анализаторов Roslyn-а, но я решил начать с чего-то попроще.

Цель недельного проекта была такая: сделать простой анализатор, который будет показывать типовые ошибки обработки исключений. Самые болезненные с моей точки зрения такие:

  • Повторная генерация исключений с помощью throw ex;
  • “Проглатывание” всех исключений с помощью пустых блоков catch {} или catch(Exception) {}.
  • “Проглатывание” исключений в определенных ветках блока catch.
  • Сохранение в логгах только сообщения ex.Message, теряя при этом потенциально важную информацию о месте возникновения исключения.
  • Некорректное пробрасывание новых исключений из блока catch.

понедельник, 2 марта 2015 г.

Закрытый конструктор базового класса

Вопрос: может ли конструктор абстрактного базового класса Base быть закрытым? Возможно ли в этом случае создать класс-наследник Derived и его экземпляр?

abstract class Base
{
   
// WTF! Что с этим делать!
    private
Base()
    {
       
Console.WriteLine("B.ctor");
    }
}

Очевидно, что я бы не задавал этот вопрос, если бы сделать это было невозможно. Существует два способа создать наследника базового класса с закрытым конструктором: один вполне вменяемый и второй, совершенно невменяемый.

понедельник, 22 декабря 2014 г.

Фильтры исключений в C# 6.0

Одной из новых возможностей языка C# 6.0 являются фильтры исключений.

Общая идея довольно простая: рядом с блоком catch появляется возможность задать некоторый предикат, который будет определять, будет ли вызван блок catch или нет.

clip_image002

Данный вариант синтаксиса доступен в публичной версии VS2015, но он будет изменен в финальной версии языка C#. Вместо if будет использоваться ключевое слово when.

среда, 12 ноября 2014 г.

Когда предусловия не являются предусловиями

UPDATE: библиотека Code Contract знает о тонкостях реализации таких возможностей как async/await и блоков итераторов. Поэтому описанные ранее проблемы касаются лишь старых (legacy) предусловий, основанных на if-throw. Если вы зашли сюда первый раз, то просто не обращайте внимания на этот абзац!

Что такое предусловие? Это некоторое утверждение, которое должно быть истинным во время вызова метода, причем за его истинность отвечает вызывающая сторона. Предусловия включают в себя проверку аргументов или внутреннего состояния объекта, а его нарушение проявляется в генерации вызываемым кодом исключений ArgumentException для невалидных аргументов, и InvalidOperationException для вызова метода в невалидном состоянии объекта.

Другими словами, предусловия гарантируют, что вызывающий код находится в нужном состоянии и ему передали все нужные данные для выполнения своей работы:

// class CustomStream
public Task<string> ReadString(int
position)
{
   
if (position < 0
)
       
throw new ArgumentOutOfRangeException("position"
);
   
if
(CanRead)
       
throw new InvalidOperationException("Stream is not readable"
);

   
return Task.FromResult("42");
}

понедельник, 2 июня 2014 г.

Борьба с "нулевыми" ссылками в C#

UPDATE: интересно продолжение этой статьи? Читайте: “Контракты vs. Монады?”.

Вступление

В моих черновиках уже больше года лежит статья, в которой я хотел рассказать о проблеме разыменовывания пустых ссылок (null reference dereferencing), с подходами в разных языках и платформах. Но поскольку у меня все никак не доходили руки, а в комментариях к прошлой статье ("Интервью с Бертраном Мейером") была затронута эта тема в контексте языка C#, то я решил к ней все-таки вернуться. Пусть получилось не столь фундаментально как я хотел изначально, но букв и так получилось довольно много.

Ошибка на миллиард долларов?

В марте 2009-го года сэр Тони Хоар (C.A.R. Hoare) выступил на конференции Qcon в Лондоне с докладом на тему "Нулевые ссылки: ошибка на миллиард долларов" (Null References: The Billion Dollar Mistake), в котором признался, что считает изобретение нулевых указателей одной из главных своих ошибок, стоившей индустрии миллиарды долларов.

среда, 7 мая 2014 г.

Паттерн Итератор

Пред. паттерн: Паттерн Посредник

Назначение: представляет доступ ко всем элементам составного объекта, не раскрывая его внутреннего представления.

Подробнее – Iterator on Wiki

Мотивация

Практически любое приложение в той или иной мере работает с коллекциями данных. Мы постоянно используем векторы, списки, деревья и хеш-таблицы. В некоторых случаях для обработки данных коллекции мы используем специфический API конкретных коллекций, но в большинстве случаев мы получаем доступ к их внутренностям за счет специального абстрактного слоя – итераторов.

вторник, 22 апреля 2014 г.

Книги для изучения C#/.NET

DISCLAIMER: выбор хорошей книги – это довольно сложный и индивидуальный процесс. Список ниже основывается на моем личном опыте и обилии "перекрестных ссылок" из разных книг, статей, обсуждений в форумах и личных обсуждениях с коллегами. Если вдруг вы не увидите свою любимую книгу в этом списке, это не значит, что она его недостойна, возможно, она просто аналогично одной из книг, представленных ниже.

Пришло время немного переформатировать старый пост с "классическими книгами по C#/.NET", чтобы отразить не просто список достойных источников для изучения C#/.NET, но и упростить выбор нужных книг в зависимости от уровня и потребностей читателя.

среда, 12 марта 2014 г.

RAII в C#. Локальный Метод Шаблона vs. IDisposable

Пред. запись: Шаблонный Метод
След. запись: Паттерн Посредник

Данная статья является продолжением обсуждения паттерна Шаблонный Метод, начатого в прошлый раз.

Одной из ключевой идиом языка С++ является идиома RAII – Resource Acquisition Is Initialization. Главная ее идея заключается в том, что некоторый ресурс, например, память, дескриптор, сокет и т.п. захватывается в конструкторе и освобождается в деструкторе. А поскольку деструкторы локальных объектов вызываются обязательно, независимо от того, по какой причине управление покидает текущую область видимости, мы получаем полуавтоматическое управление ресурсами.

При этом «автоматическое управление» применяется не только для ресурсов – памяти, дескрипторов, файлов или сокетов, но и для других целей. Так например, автоматический вызов деструктора используется в многопоточных приложениях для реализации критических секций в коде. Для этого используются классы std::mutex, а также класс std::unique_lock, который захватывает блокировку в конструкторе и освобождает в деструкторе:

понедельник, 29 июля 2013 г.

О книге Билла Вагнера “Effective C#”

Effective CSharpЯ уже много лет являюсь поклонником серии "Effective XXX", начатой Скотом Мейерсом в 1997-м году с его “Effective C++”. Книги из этой серии содержат несколько десятков советов о вашем любимом языке программирования, рассказыавя о том, что делать стоит, а чего – нет. Такие книги легко читать, и они являются отличным источником для размышлений.

И хотя эти книги являются раем для читателя, их невероятно сложно писать. Чтобы понять это, достаточно попробовать написать статейку из серии "Используйте/не используйте эту возможность языка C#" или просто вспомните какой-нибудь холи-ворчик у себя в коллективе, который начался невинной фразой, "а давайте везде будем использовать as вместо оператора приведения типов" или другой подобной фразы.

Проблема любых советов из серии используй/не используй/избегай в том обилии исключений, которые следуют за любым подобным правилом. Вот, например, стоит ли использовать изменяемые значимые типы (mutable value types)? Любой программист, пришедший в .NET из С++ ответит положительно (ведь так быстрее!), потом он прочитает о проблемах и его мнение наверняка изменится на противоположное. После чего, в его голове может возникнуть барьер, который он уже не сможет преодолеть даже тогда, когда ему нужно будет пожертвовать безопасностью в угоду эффективности и использовать структуры в своем коде.

среда, 10 июля 2013 г.

О времени вызова статических конструкторов

Статические конструкторы являются одной из самых странных возможностей C# и CLR и многие годы я не понимал их достаточно хорошо, но поскольку Джон (Скит) внес дополнительные разъяснения в мое понимание этой возможности, я все еще явно не до конца ее понимаю!
Эрик Липперт “Static constructors, part three”

Вопрос: в каком порядке будут вызваны экземплярные и статические конструкторы классов B и D при создании экземпляра класса D?

class B { }

class D : B { }

Ответ: конструкторы экземпляров вызываются от базового к наследнику, а статические конструкторы – it depends!

пятница, 28 июня 2013 г.

Взаимоблокировки в статических конструкторах

Поскольку в Гугл+ так и не нашлось объяснения странного поведения приведенного там кода, то я решил рассказать об этом более подробно.

Итак, вопрос заключается в следующем: что мы ожидаем увидеть при исполнении следующего кода и что мы увидим на самом деле?

class CrazyType
{
   
public static readonly int
Foo = GetFoo();

   
private static int
GetFoo()
    {
       
return Task
.Run(() => 42).Result;
    }
}

class Program
{
   
static void Main(string
[] args)
    {
       
Console.WriteLine("Main method get called!"
);
       
Console.WriteLine(CrazyType.Foo);
    }
}

понедельник, 24 июня 2013 г.

Аргументы по умолчанию в C#

«При наличии общего механизма перегрузки функций аргументы по умолчанию становятся логически избыточными и в лучшем случае обеспечивают небольшое удобство нотации»
                                                                                            Б. Страуструп «Дизайн и эволюция С++»

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

четверг, 13 июня 2013 г.

О пользовательских преобразованиях типов

Поскольку мне говорят (точнее пишут), что я немного утомил с философией программирования и стоит браться за ум вспомнить и о технологиях, то я решил не откладывать это дело в долгий ящик, и опубликовать несколько заметок о языке C#. Тем более, что после подготовки к Hot Code остались некоторые наработки, которыми будет интересно поделиться.

Итак, у нас есть структура BigDouble с оператором неявного приведения типов к double и список этих структур:

struct BigDouble
{
private readonly double
_value;

public BigDouble(double
value) { _value = value; }
public static implicit operator double
(BigDouble value)
{
return
value._value; }
}


var bigDoubles = new List
<BigDouble>
{
new
BigDouble(42.0),
new BigDouble(18.0),
};

Вопрос #1. Будет ли работать следующий цикл foreach?
foreach (double d in bigDoubles)
{
   
Console.WriteLine(d);
}
Вопрос #2. Что мы получим в следующих случаях?
var query = from double d in bigDoubles
           
select
d;

foreach (var d in query) { Console.WriteLine(d); }

четверг, 18 апреля 2013 г.

Расширение типов в F#

В языке C# существует возможность расширять существующие типы методами расширения, синтаксис вызовов которых похож на вызов экземплярных методов. На этом построен весь LINQ, да и в обычной жизни методам расширения находится полезное применение.

F# также поддерживает возможность расширения существующих типов, но принцип работы и логика этого всего дела несколько иная. В F# не существует таких понятий, как методы расширения, свойства расширения и т.п., вместо этого существует общее понятие под названием "расширение типов" (type extension). Причем под этим термином подразумевает два разных явления: intrinsic extensions ("внутренние расширения") и optional extensions ("необязательные расширения").

И хотя синтаксис расширений совершенно одинаковый, семантика у них принципиально разная. Intrinsic extensions – это аналог частичных типов в C#, а optional extensions – аналог методов расширения, но в более расширенном виде, поскольку мы можем добавить в существующий тип не только «экземплярные» методы, но и статические методы, свойства или события.