Программерские типсы и триксы: потайные ходы в подземелье C#Александр Эккерт (stannic.man@gmail.com) Говорят, лень - двигатель прогресса. У тебя тоже есть свой двигатель прогресса - журнал "][", который посредством этой статьи поможет тебе сделать код на C# предметом зависти и восхищения собратьев по цеху :). Сегодня я постарался рассмотреть некоторые малоизвестные широкой публике моменты, связанные с программированием на C#, которые помогут тебе избежать некоторых ошибок и сделать твои приложения гораздо более гибкими и адекватными. Константы и поля только "для чтения"В стандарте C# предусматриваются поля и константы readonly ("только для чтения"):
В чем же между ними разница? Константы вычисляются на стадии компиляции программы, а значения "readonly" - только на стадии выполнения программы. Неочевидные последствия такого отличия могут проявиться в том случае, когда, скажем, библиотека и использующая ее программа компилируются отдельно. Если в библиотеке нужно использовать константу, то при изменении ее значения (перекомпиляции библиотеки), нужно будет пересобирать и саму программу. При использовании простых readonly-полей, перекомпилировать программу уже не нужно. Оператор "IS" или метод IsSubclassOf()?Какая разница при использовании оператора is и метода IsSubclassOf()? Ведь, казалось бы, оба они несут одну и ту же функциональность? Разбираемся с приведением типовВ C# существуют два вида приведения типов: использованием оператора "as" и прямое приведение. Практика показывает, что в коде гораздо предпочтительнее использовать прямое приведение типов и вот почему. Если система по какой-то причине откажется проглатывать прямое приведение типов, то будет сгенерировано исключение NullReferenceException, тогда как оператор as просто вернет null. Во втором случае существует большая вероятность, что после получения нулевой ссылки от оператора as, где-то глубоко в недрах твоей программы будет сгенерировано исключение NullReferenceException, отыскать причины которого будет уже не так-то просто. В случае прямого приведения типов причина ошибки будет очевидной. Разница между полем и свойством в C#Очень часто возникает вопрос: «а в чем разница между полем и свойством в C#»? С точки зрения доступа - обычное поле и свойство с доступом get/set ничем не отличаются. Поэтому, ответ на вопросы «что предпочтительней» и «как именно использовать» зависит от контекста программы. Во-первых, использование свойства нужно, когда необходимо при установке свойству определенного значения, выполнить какие-то действия:
Во-вторых, использование свойства требуется в том случае, когда нужно проверить присеваемое свойству значение:
Ну и в-третьих, использование свойства целесообразно, когда значение свойства не хранится в классе, а, например, считывается из базы данных:
Перехват Win32-сообщенийЧасто в твоем приложении возникает необходимость перехватить и использовать для своих нужд сообщения Win32. Как это сделать на C#? Очень легко - достаточно задействовать в своем коде интерфейс Windows.Forms.IMessageFilter. Параметры сообщения будут доступны в свойствах m.LParam и m.WParam: Ловим Win32-сообщения Недостаток этого перехвата в том, что тебе придется расшифровывать сообщения по их коду. Защита от переполнения - ключевые слова checked и uncheckedВ C# существуют такие ключевые слова - checked и unchecked, использование которых поможет предотвратить переполнение целого и повысить безопасность твоего кода. Можно объявить checked-блок: Контролируем переполнение целого числа В данном случае, приведение a + b от int к byte приведет к исключению. В строке с Convert.ToByte, исключение возникло бы и без ключевого слова checked, но его наличие приводит к возникновению исключения еще и при вычислении аргументов метода Console.WriteLine(). Поскольку иногда переполнение целого числа допускается намеренно, то имеется ключевое слово unchecked, которое отключает контроль за переполнением. Ключевые слова checked и unchecked можно использовать для включения/отключения контроля в одном выражении:
Наконец, можно включить контроль за переполнением с помощью флага "/checked" компилятора - если этот флаг присутствует, то нужно явно помечать словом unchecked те участки кода, где переполнение допустимо. Равенство и тождество объектовОчень часто разработчикам приходится писать код для сравнения двух объектов (к примеру, при поиске, сортировке и сравнении отдельных элементов набора (массива). Справедливости ради надо сказать, что с определением равенства и тождественности в .NET Framework дела обстоят не совсем ладно. У типа System.Object есть метод Equales, который возвращает true для двух "равных" объектов. Вот как выглядит стандартная реализация этого метода в типе Object: Стандартная реализация метода Equals Код выглядит очень просто, однако возникает вопрос - а если аргументы ссылаются на разные объекты? В таком случае методу Equals гораздо сложнее определить, содержат ли объекты одинаковое значение. Иначе говоря, указанная выше реализация метода Equals у типа Object реализует проверку на тождество, но не равенство и поэтому никуда не годится. Поэтому сейчас мы напишем свой, грамотный вариант реализации метода Equals: Грамотная реализация метода Equals Этот метод вполне может использоваться для переопределения метода Equals() в твоем коде для получения равенства типов. Для проверки на тождественность в .NET предусмотрен метод, и ты его, уверен, знаешь - Object.ReferenceEquals(). Немного об оптимизации кодаСуществует два параметра компилятора C#, которые влияют на оптимизацию кода - /optimize и /debug. В приведенной таблице ты можешь увидеть варианты их использования - как говорится, "почувствуйте разницу" Если твои эксперименты покажут, что JIT-компилятор CLR не обеспечивает нужной производительности, воспользуйся утилиткой ngen.exe, поставляемой в комплекте .NET Framework SDK. Она компилирует весь IL-код выбранной сборки в машинный код и сохраняет его на диске. При запуске сборки, CLR автоматически проверит наличие предварительно скомпилированной версии сборки, и, если она есть, загрузит ее, пропустив предварительную компиляцию. К вопросу о потокахНе могу не затронуть вопрос об эффективном использовании потоков в приложении - часто приходилось видеть, как из мощного оружия улучшения производительности, злоупотребления с потоками приводили к обратному результату. Если говорить точно, то использование потоков всегда приводит к издержкам в работе системы, потому что процессор тратит уйму времени и ресурсов при создании потоков и переключении контекста между ними. С появлением многопроцессорных (многоядерных) систем эта проблема отошла на второй план («правильно, а чего мелочиться с потоками, у меня четыре проца их проглотят и не подавятся ;)»), но тем не менее, грамотное использование поточных моделей в .NET-приложениях будет играть на руку разработчику, не говоря уже о росте его профессионализма. В .NET существует готовое решение для управления собственным пулом потоков. Пул потоков позволяет разработчику найти золотую середину - с одной стороны, малое число потоков экономит ресурсы, а большое - позволяет со всем размахом воспользоваться преимуществами многопроцессорных систем; Пул потоков просто приспосабливается к текущей ситуации: если нужно выполнить много задач и в системе есть больше одного процессора, CLR создаст новые потоки. При уменьшении загруженности, количество потоков будет уменьшено. И тут в руки программиста попадают методы регулирования их количества - SetMaxThreads и GetMinThreads. Как правило, практика показывает, что на работу приложения очень негативно влияет вызов функции SetMaxThreads, потому что манипуляции с максимальным количеством потоков ухудшают, а не улучшают производительность приложения. Если же тебе в приложении требуется больше 25 потоков на процессор, то следует задуматься об архитектуре программы и способах использования потоков в ней. Если же тебе в приложении требуется больше 25 потоков на процессор, то следует задуматься об архитектуре программы и способах использования потоков в ней. Вместе с этим, стоит отметить, что в CLR заложен механизм предотвращения слишком частного создания потоков - разрешается создавать не более одного потока в 500 мс. Если тебя такой вариант не устраивает, в приложение можно добавить вызов SetMinThreads, которому можно передать минимальное допустимое число потоков в пуле. Пул быстро создаст указанное число потоков, а есели при появлении в очереди новых заданий все потоки будут заняты, он продолжит создание потоков со скоростью не более одного потока в 500 мс. |