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

среда, 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.

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

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

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

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

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

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

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

вторник, 27 августа 2013 г.

О сборке мусора и достижимости объектов

DISCLAIMER: это относительно продвинутая статья о сборке мусора, поэтому автор предполагает минимальное знакомство читателя с принципом работы сборщика мусора CLR.

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

Поскольку объект не может контролировать процесс своего уничтожения, то этот вопрос можно перефразировать так: может ли финализатор вызваться до окончания вызова конструктора?

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

среда, 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);
    }
}

среда, 20 февраля 2013 г.

MVP Summit. День 1. Об эффективности

В большинстве приложений оптимизации, о которых я писал в прошлый раз, совершенно неинтересны: будет ли экземпляр делегата создаваться каждый раз или он будет создаваться лишь первый раз и сохраняться в кэше. Но в таких проектах, как РеШарпер, Visual Studio или компилятор, этому придается важное значение, поэтому не удивительно, что во многих сессиях вопросы эффективности так или иначе затрагиваются.

Я не могу сказать, что такое же внимание к эффективности требуется для “обычных” проектов, тем не менее, понимание того, что происходит за кулисами может помочь тогда, когда в этом появится необходимость. Вот на парочке подобных примеров я и хочу остановиться.

понедельник, 18 февраля 2013 г.

MVP Summit. День 0 + кэширование делегатов в C#

В этом году я первый раз попал на MVP Summit, который ежегодно проводит Майкрософт для своих MVP. Он длится 3 дня и многое, что на нем рассказывается попадает под NDA (Non-Disclosure Agreement) и разглашению не подлежит. Но помимо закрытой информации, тут сами MVP делятся друг с другом полезными советами, да и то, что рассказывают сотрудники МС-а довольно часто доступно публично.

Сегодня никакой особой программы не было, помимо регистрации и стартового мероприятия (знакомства всех со всеми), но для dev подразделения было сделано небольшое исключение в виде QA-сессии со Стивеном Таубом (Stephen Toub) и Lucian Wischik (даже не буду пытаться транслитить). Это была чистейшей воды ad hoc сессия на которой обсуждали практически любые вопросы, связанные с асинхронностью и не только (даже успели потролить на тему Ambient Context vs Dependency Injection).

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

О вреде изменяемых значимых типов. Часть 2

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

среда, 19 сентября 2012 г.

Структуры и конструкторы по умолчанию

Я решил немного развить тему, поднятую в Google+ о том, почему большинство языков программирования для платформы .NET не позволяют объявлять конструкторы по умолчанию для структур (т.е. для значимых типов).

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

Вот простой пример, как вы ответите на следующий вопрос: сколько значимых типов из .NET Framework содержит конструкторы по умолчанию? Интуитивным ответом кажется "все", и будете не правы, поскольку на самом деле, ни один из значимых типов .NET Framework не содержит конструктора по умолчанию.

вторник, 7 августа 2012 г.

Перегрузка и наследование

Существует определенный набор возможностей в любом языке программирования для понимания которых нужно просто знать, как они реализованы. Вот, например, замыкания; это не сверх сложная концепция, но знание того, как этот зверь устроен позволяет делать определенные выводы относительно поведения замыканий с переменными цикла. Тоже самое касается вызова виртуальных методов в конструкторе базового класса: здесь нет одного правильного решения и нужно просто знать, что именно решили разработчики языка и будет ли вызываться метод наследника (как в Java или C#), или же «полиморфное» поведение в конструкторе не работает и будет вызываться метод базового класса (как в С++).

Еще одним типом проблемы у которой нет идеального решения, является совмещение перегрузки методов (overloading) и переопределения (overriding) метода. Давайте рассмотрим следующий пример. Предположим, у нас есть пара классов, Base и Derived, с виртуальным методом Foo(int) и невиртуальным методом Foo(object) в классе Derived:

среда, 1 августа 2012 г.

Duck typing или “так ли прост старина foreach?”

Я думаю, что многие разработчики знают, что цикл foreach в языке C# не так прост, каким он кажется на первый взгляд. Для начала давайте ответим на вопрос: «А что нужно, чтобы конструкция foreach успешно компилировалась?». Интуитивным ответом на этот вопрос кажется что-то типа: «Реализация классом интерфейса IEnumerable или IEnumerable<T>.». Однако, это не так, ну, или не совсем так.

Полный ответ на этот вопрос такой: «Для того чтобы конструкция foreach успешно компилировалась необходимо, чтобы у объекта был метод GetEnumerator(), который вернет объект с методом MoveNext() и свойством Current, а если такого метода нет, то тогда будем искать интерфейсы IEnumerable и IEnumerable<T>».

Причин у такого «утиного» поведения две.

понедельник, 7 мая 2012 г.

Инициализаторы объектов в блоке using

UPDATE: наткнулся на объяснение данного поведения Эриком Липпертом (подробности здесь).

Инициализаторы объектов (Object Initializers) – это полезная возможность языка C#, которая позволяет инициализировать необходимые свойства объекта прямо во время его создания. Поскольку синтаксически эта «фича» очень близка к инициализации объекта с передачей параметров через конструктор, многие разработчики начинают забивать на принципы ООП (в частности на понятие инварианта) и использовать ее, где только можно.

Но даже если не переходить к холиварам и малопонятным терминам, давайте рассмотрим небольшой пример, и подумаем над тем, может ли он привести к проблемам или нет:

// position передается извне или настраиватся каким-то образом
long position = -1;
using (var file = new FileStream("d:\\1.txt", FileMode
.Append)
                        {
                           
// Мы точно знаем, что нужные данные расположены
                            // с некоторым сдвигом!
                            Position = position
                        })
{
   
// Делаем чего-то с файлом
}

В данном фрагменте внутри директивы using создается ресурс (файл) и устанавливается одно из его свойств (Position) с помощью инициализатора объекта. При этом самое главное в этом коде то, что setter этого свойства может генерировать исключение.

понедельник, 2 апреля 2012 г.

Замыкание на переменных цикла в C# 5.0

Многие разработчики языков программирования, библиотек, да и классов простых приложений стремятся к интуитивно понятному интерфейсу создаваемых классов. Скотт Мейерс еще полтора десятка лет назад сказал о том, чтобы мы стремились разрабатывать классы (библиотеки, языки), которые легко использовать правильно, и сложно использовать неправильно.

Если говорить о языке C#, то его разработчики подходят к вопросам «юзабилити» весьма основательно; они спокойно могут пожертвовать «объектной чистотой» в угоду здравому смыслу и удобству использования. Одним из немногих исключений из этого правила является замыкание на переменной цикла, той самой фичи, которая ведет себя не так, как считают многие разработчики. При этом количество недовольства и недопонимания настолько много, что в 5-й версии языка C# это поведение решили изменить.

понедельник, 1 августа 2011 г.

О синглтонах и статических конструкторах

Изначально автор хотел назвать эту статью следующим образом: «О синглтонах, статических конструкторах и инициализаторах статических полей, о флаге beforeFieldInit и о его влиянии на deadlock-и статических конструкторов при старте сервисов релизных билдов в .Net Framework 3.5», однако в связи с тем, что многострочные названия по неведомой автору причине так и не прижились в современном компьютерном сообществе, он (автор) решил сократить это название, чудовищным образом исказив его исходный смысл.

-------------------------

Любая реализация паттерна Синглтон в общем случае преследует две цели: во-первых, реализация должна быть потокобезопасной, чтобы предотвратить создание более одного экземпляра в многопоточном мире .Net; а во-вторых, эта реализация должна быть «отложенной» (lazy), чтобы не создавать экземпляр (потенциально) дорого объекта раньше времени или в тех случаях, когда он вообще может не понадобиться. Но поскольку основное внимание при прочтении любой статьи про реализацию Синглтона отводится многопоточности, то на «ленивость» зачастую не хватает ни времени не желания.

понедельник, 18 июля 2011 г.

О вреде изменяемых значимых типов

Большинство программистов, которых нелегкая судьба свела с платформной.Net знают о существовании значимых типов (value types) и ссылочных типов (reference types). И довольно многие из них прекрасно знают, что помимо названия, эти типы имеют и другие различия, такие как расположение объектов оных типов в памяти, а также в семантике.

Что касается первого различия (о котором стоит упомянуть как минимум ради полноты изложения), то оно заключается в том, что экземпляры ссылочных типов всегда располагаются в управляемой куче, в то время как экземпляры значимых типов по умолчанию располагаются в стеке, но могут мигрировать в управляемую кучу вследствие упаковки, будучи членами ссылочных типов, а также при использовании их в хитрых экзотических конструкциях языка C#, типа замыканий (*).

Хотя это отличие является очень важным и именно благодаря нему значимые типы существуют и используются, у этой пары типов есть еще одно, не менее важное семантическое различие. Значимые типы, как подсказывает название, являются значениями, которое копируется каждый раз при передаче в функцию или при возвращении из нее. А поскольку при копировании, как, опять же, подсказывает название, передается и возвращается не исходный вариант, а копия, то все попытки изменений приведут к изменению копии, а не исходного экземпляра.

понедельник, 28 февраля 2011 г.

this == null в языке C#?!

Несмотря на то, что язык C# во многих вопросах шагнул на несколько шагов вперед по сравнению со старым добрым С++, в них все еще осталось достаточно много общего. В языке C#, как и в С++, экземлярный метод класса отличается от статического благодаря неявной передаче указателя на экземпляр этого класса (a.k.a. this). Этот анахронизм хорошо спрятан от глаз, но все же он иногда проявляет себя, особенно при работе с делегатами с помощью рефлексии, когда для вызова статического метода мы передаем null, в качестве одного из параметров, а для вызова экземплярного метода, мы передаем некоторый объект, чей экземплярный метод мы хотим вызвать.

Поскольку каждый экземплярный метод все еще неявным образом получает ссылку на текущий объект (в виде неявного параметра this), то возникает вопрос, а может ли быть ситуация, когда этот самый параметр this при вызове экземплярного метода равен null, и, соответственно, насколько логично такая проверка в экземплярном методе?

понедельник, 21 февраля 2011 г.

Виртуальные события в языке C#

Вы когда-нибудь задавались вопросом о том, могут ли события быть виртуальными? Вполне возможно вас самих эта мысль никогда не посещала; возможно, вам в этом помог какой-нибудь дотошный парень на собеседование, в общем, не очень-то важно, думали вы об этом или нет, давайте подумаем над этим вопросом совместно прямо сейчас.

Итак, события в языке C# по сути являются реализацией известного паттерна publish/subscribe и содержат всего пару методов add и remove, для подписки и отписки от события, и закрытое поле с мультикаст делегатом, который, собственно, этих самых подписчиков и содержит. А раз событие – это по сути методы, а методы могут быть виртуальными, можно сделать вывод, что события тоже могут быть виртуальными (тем более что свойства ведь могут быть виртуальными и этот факт не вредит ничьему ментальному здоровью). Итак, теоретически – с виртуальными событиями все должно быть нормально, однако это как раз тот случай, когда теория с практикой несколько расходятся.

понедельник, 17 января 2011 г.

О вреде метода Thread.Abort

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

Давайте в качестве примера рассмотрим различные сопособы прекращения работы созданного вручную потока. Если вы начали знакомство с многопоточностью, с таких чудесных библиотек как WinAPI и не менее чудесных языков программирования как С и С++, то вы наверняка очень быстро поняли, что завершать выполнение потока с помощью вызова функции TerminateThread мягко говоря не стоит. Вызов функции TerminateThread гарантировано приводит к утечкам ресурсов, памяти, некорректному состоянию объектов ядра операционной системы и еще десятку других напастей, после которых корректное состояние приложения – мало вероятно. Вызов этой функции достаточно быстро завоевал дурную славу у разработчиков (благо даже в официальной документации на эту функцию в MSDN черным по английскому сказано, что вызывать ее не стоит) и большинству разработчиков пришлось искать другие способы завершения работы потока, начиная от уведомление рабочего потока с помощью событий (events), заканчивая применением булевого флага, который проверяется рабочим потоком и устанвливается в true для его завершения.

среда, 2 июня 2010 г.

Распаковка (unboxing) и InvalidCastExcpetion

Несмотря на то, что упаковка и распаковка (boxing/unboxing) стала встречаться значительно реже в повседневной практике разработчика после появления обобщенных (generic) коллекций в C# 2.0, эта тема все еще остается одной из самых коварных и малопонятных для многих, поскольку поведение во время выполнения далеко не всегда является интуитивно понятным и ожидаемым с их точки зрения.

Классическим примером ошибок, связанных с упаковкой/распаковкой является изменения не того экземпляра значимого типа (value type), когда в результате выполнения некоторого кода изменяется не требуемый объект, а всего лишь его копия (именно это и является причиной того, что изменяемые (mutable) структуры являются главным вселенским злом). Другим примером является неочевидное для многих поведение, когда при распаковке объекта одного типа в переменную другого типа генерируется исключение InvalidCastException.

суббота, 10 апреля 2010 г.

Замыкания в языке программирования C#

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