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

воскресенье, 15 мая 2011 г.

Я был на Ciklum .Net Saturday

Если коротко, то когда я ехал в Киев, то электричка громко пела: туду-туду, туду-туду, туду-туду. На встрече #Ciklum .Net Saturday, докладчики говорили: TDD-TDD, TDD-TDD, TDD-TDD.
Возвращению домой, я таки заметил, что на самом деле звук электрички был: тыдыды-тыдыды, тыдыды-тыдыды, TDD-TDD ;)

Доклады на конференции шли в два потока, так что, к сожалению, посмотреть все и сразу у меня не вышло. Расскажу о том, что я видел собственными глазами:

Enterprise architecture in gaming environment

Камрад Owe Laestadius, который работает «Team Lead at Betsson» (хотя, позиция эта туманная, и чем он там на самом деле занимается я не знаю, берите поправку на Agile ;) кратко, но очень емко рассказывал об архитектуре и о том, почему архитектура не должна быть статичной, т.е. сделанной один раз на 100 лет, а гибкой, модульной и готовой к изменениям.
Owe говорил, что хорошие решения и реализации получаются обычно с третьего раза. Пример был такой:
  • В Системе был реализован только один метод электронных платежей через кредитные карточки Visa.
  • Появилась необходимость добавить поддержку PayPal. Создали новый модуль + добавили специализированный код для поддержки PayPal.
  • Появилась необходимость поддержки еще одной системы онлайн платежей. На этот раз, общий код для поддержки электронных платежей был перенесен в отдельный модуль, а лишний код удален. Система стала модульной и была способна поддерживать добавление новых платежных систем без лишних трудностей и лишнего кода.
Это вот пример того, что все хорошее получается почему-то с третьего раза. Я не знаю почему, но и за собой замечал эту особенность.

Continuous Integration: What? Why? & How?

Валерий Пелехов (Betsson) очень легко и просто рассказал о применении Continuous Integration в Betsson. Я специально не хочу пересказывать презентацию, хоть и заметок у меня получилось достаточно много, потому что всю эту информацию можно найти и в сети. Валерий показал на примере реального проекта, как у них используется CI и уверенно доказал что это действительно очень удобно и что команда в любой момент времени может сказать что их код либо работает, либо не работает. Варианта «ну как бы работает, наверное» – там нет. Вот именно это сложно передать текстом, и лучше один раз услышать и увидеть.
Из технических деталей я узнал, что на Betsson используется CI система TeamCity, а тесты на xUnit.

(Integration + Deployment) * Continues = (Continuous Production)

Александр Белецкий рассказывал о том, как он сократил время недоступности своего проекта во время разворачивания новой версии на продакшн сервер для пользователей с 30 минут до 1-й секунды. Его конфигурация, используют CI систему Jenkins, исходники проекта хранятся на Github, для юнит тестов – NUnit + вспомогательные сборки, UppercuT и RoundhousE – которые позволяют билдить и разворачивать новый бил + апгрейдить базу данных в один клик.
Более детально вы можете узнать обо всем из Блога Александра.

Хотите драйва? Вам в TDD!

Сергей Калинец сделал замечательный доклад о TDD и о драйве. Рассказал, почему TDD – это драйв, об ошибках и проблемах внедрения TDD. Я называю такие доклады «мотивирующими»: доклад, который лучше один раз увидеть, чем сто раз прочитать. Рекомендую почитать материалы его блога TDD 4 .Net, где можно почерпнуть достаточное количество качественной информации по теме TDD. Презентация доклада тут: Хотите драйва? Вам в TDD!

Approaches to sharing files between .NET

Александр Винниченко провел исследование о передачи файлов по сети средствами .NET, используя общую папку на сети, и WCF (HTTP binding). Как оказалось, WCF оказался не намного худшим способом для передачи файлов, и для файлов небольшого размера ненамного отставал по скорости, сравнительно с расшареной папкой на сети.

Test Driven Development in ASP.NET MVC 3

Антон Видищев рассказывал о сложностях внедрения TDD в ASP.NET MVC 3. Показал, как лучше писать тесты и как использовать Fakes, Mocks, Stubs и Dummies. По началу, внедрять TDD в проект было реально сложно, но в данный момент, команда разработчиков добилась той же скорости и продуктивности что и до TDD и команда может уверенно ответить в любой момент: код работает правильно или нет.

Как опубликовать свое приложение в Windows Phone 7

Александр Краковецкий (http://msug.vn.ua/Members/Details/2100) – это действительно профессиональный докладчик, который следит за всеми новинками Microsoft, и кроме того, проводит исследование и пишет качественные статьи на Хабре, msug.vn.ua и множестве других источников.
В своем докладе, он рассказал о публикации собственных приложений на WP7 Marketplace. Продемонстрировал, что используя обходные пути, все это достижимо для украинского разработчика. Меня удивил тот факт, что процесс валидации приложения на стороне Microsoft занимает не больше 3-х дней, это действительно очень короткий срок.
Но, лично я не принимаю факт закрытости и жесткого контроля Microsoft над процессом разработки для программистов WP7. Да, возможно правила Microsoft не жестче чем у Apple, но и преимуществ эти ограничения платформе не добавляют.
iOS и Android уже прочно заняли свои места на рынке. Apple как всегда делают свои продукты для небедных людей, которые готовы платить за бренд. Android – собственно, платформа людей с самыми разными финансовыми возможностями, моделей телефонов их производителей сейчас множество. А для разработчика платформа Android – открыта и лишена множества искусственных ограничений.
Но, не мне судить, как покажет себя WP7. Поживем – увидим.
А, доклад Александра был действительно очень качественным, и я уверен, полезным тем, кто планирует начать разработку для WP7.

Огромное Спасибо компании Ciklum за организацию встречи и за активную работу по проведению подобных мероприятий. Спасибо за пиво, пиццу и приют.

четверг, 3 марта 2011 г.

А вот он где спрятался! (HttpContext.Current.Server)

private static HttpServerUtility Server = HttpContext.Current.Server;

воскресенье, 27 февраля 2011 г.

MySQL и C#: Проблема с русскими буквами при сохранении в таблицу

Наверное это самая популярная проблема в MySQL – при сохранении русского текста в таблицу, текст стает таким вот: «?????? ??????!». Т.е все русские буквы преобразовываются в знаки вопроса, что говорит о проблеме кодировок.

MySQL C# Text Encoding Problems

Самым популярным решением этой проблемы было выполнить следующий запрос для установки необходимой кодировки после подключения:
SET NAMES utf8

Это и у меня работало в Perl и PHP, а вот в C# – не сработало.
И тут-то пошли извращения, дошло даже до такого:

string UtfConv(string strToConvert)
{
return System.Text.Encoding.UTF8.GetString(System.Text.Encoding.Default.GetBytes(strToConvert));
}


Хорошо, что этот способ не сработал.
А оказалось, что стоит лишь немного подправить ConnectionString, указав в нем заветную кодировку:

И MySQL ConnectionString превращается…:
"server=localhost;uid=root;pwd=root;database=deeprss "

… превращается в следующий:
"server=localhost;uid=root;pwd=root;database=deeprss;charset=utf8"


Параметр я вычитал тут:
21.2.6. Connector/NET Connection String Options Reference


Все таблицы в БД у меня в utf8.

пятница, 4 февраля 2011 г.

C# code snipped: Create Excel instance using the dynamic keyword

The code below creates dynamic Excel.Application object without referencing the Excel COM server dll.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{

dynamic excel = Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application"));
excel.Visible = true;
Console.Read();

}
}
}

четверг, 27 января 2011 г.

Почему следует избегать специализации обобщений для базовых классов и интерфейсов в C#

За этой статьей стоит одна бессонная ночь, проведенная в холодном дебагере Вижуалстудии. Я совершил ошибку. Интересную ошибку, которой спешу поделиться.

Речь пойдет об обобщениях в C#, их конкретизации и о том, что иногда не стоит доверять ни компилятору, ни собственному интеллекту, а просто не наследовать от мухи слона, заводя огромное количество ненужных абстракций.

Длинная вводная



Для начала, давайте определимся с понятиями.

Под Обобщениями я подразумеваю общие (или параметризованные) типы в языке C#. В англоязычной литературе они называются Generics. Более подробно про обобщения можно прочитать тут: Обобщения (Глава из книги “Язык программирования C# 2005 (Си Шарп) и платформа .NET 2.0”)

Под специализацией обобщения, чтобы не начинать словоблудие, я понимаю вот это:

static void PrintAllItems<T>(T collection) where T:IEnumerable
{
// foreach работает медленно, зато универсально
foreach (var item in collection)
{
Console.WriteLine("IEnumerable: " + item.ToString());
}
}

static void PrintAllItems(int[] intCollection)
{
// for работает быстрее foreach
for (int i = 0; i < intCollection.Length; i++ )
{
Console.WriteLine("int: " + intCollection[i]);
}
}
Метод PrintAllItems(T collection) - это обобщенный метод.
Метод PrintAllItems(int[] intCollection) – это специализация обобщеного метода.

И теперь, метод PrintAllItems будет работать для всех IEnumerable – типов одинаково, кроме типа int[], для которого метод будет работать немного иначе (хотя, int[] точно также реализует IEnumerable как и double):

static void Main(string[] args)
{
double[] doubleArray = { 1.1, 2.2, 3.3};
int[] intArray = { 1, 2, 3 };

PrintAllItems(doubleArray);
PrintAllItems(intArray);
}


Результат:
IEnumerable: 1.1
IEnumerable: 2.2
IEnumerable: 3.3
int: 1
int: 2
int: 3

И наконец-то, почему же таки следует избегать конкретизации обобщений для базовых классов и интерфейсов
Чем проще писать программисту код, используя такие синтаксические сладости, как перегрузки методов и обобщения, тем сложнее компилятору разрешать эти самые перегрузки. Ведь практически каждый обобщенный метод может соответствовать любому типу. Может получиться так, что приложение будет работать очень странно. Тут все зависит от вашей осторожности… или неосторожности. Именно вы (и я в том числе), ответственны за создание методов, которые, не должны… должны создавать минимальное, замешательство для программиста, использующего ваш код и для вас самих.
Будьте осторожны. Ваши обобщенные методы могут быть более обобщение того, чего от них ожидают.
Да, ваши обобщенные методы могут быть неожиданно универсальны, и поверьте, что это не всегда хорошо.

Давайте рассмотрим следующий пример. А пока вы читаете код, постарайтесь угадать, что и в какой последовательности он выведет на экран.


using System;
namespace ConsoleApplication6
{
public class MyAnimalBase
{
}
public interface ISayableAnimal
{
void Say();
}
public class MyCatDerived : MyAnimalBase, ISayableAnimal
{
#region ISayableAnimal Members
void ISayableAnimal.Say()
{
Console.WriteLine("Inside MyCatDerived.Say");
}
#endregion
}
public class AnotherCow : ISayableAnimal
{
#region ISayableAnimal Members
public void Say()
{
Console.WriteLine("Inside AnotherCow.Say");
}
#endregion
}
class Program
{
static void WriteMessage(MyAnimalBase b)
{
Console.WriteLine("Inside Say(MyAnimalBase)");
}
static void WriteMessage<T>(T obj)
{
Console.Write("Inside Say<T>(T): ");
Console.WriteLine(obj.ToString());
}
static void WriteMessage(ISayableAnimal obj)
{
Console.Write(
"Inside Say(ISayableAnimal): ");
obj.Say();
}
static void Main(string[] args)
{
MyCatDerived d = new MyCatDerived();
Console.WriteLine("01 Calling Program.Say");
WriteMessage(d);

Console.WriteLine();
Console.WriteLine(
"02 Calling through ISayableAnimal interface");
WriteMessage((ISayableAnimal)d);

Console.WriteLine();
Console.WriteLine("03 Cast to base object");
WriteMessage((MyAnimalBase)d);

Console.WriteLine();
Console.WriteLine("04 Another Type test:");
AnotherCow anObject = new AnotherCow();
WriteMessage(anObject);

Console.WriteLine();
Console.WriteLine("05 Cast to ISayableAnimal:");
WriteMessage((ISayableAnimal)anObject);
}
}
}

И вывод будет следующим:
01 Calling Program.Say
Inside Say(T): ConsoleApplication6.MyCatDerived

02 Calling through ISayableAnimal interface
Inside Say(ISayableAnimal): Inside MyCatDerived.Say

03 Cast to base object
Inside Say(MyAnimalBase)

04 Another Type test:
Inside Say(T): ConsoleApplication6.AnotherCow

05 Cast to ISayableAnimal:
Inside Say(ISayableAnimal): Inside AnotherCow.Say

По себе знаю, уважаемый читатель, что вы, скорее всего не бросились анализировать скучный код, сопоставляя его с результатом выполнения.
Давайте разберемся вместе, что же все-таки произошло.
Первый тест показывает, и это очень важно запомнить, то, что WriteMessage(T obj) лучше соответствует типу, чем WriteMessage(MyAnimalBase b), для объекта, который унаследован от MyAnimalBase. Это потому, что компилятор идет по пути наименьшего сопротивления, и создает метод, который абсолютно соответствует типу MyCatDerived, превращая T в MyCatDerived.
А вот метод WriteMessage(MyAnimalBase b) требует преобразования типа. Это сложнее для компилятора, а значит -- WriteMessage(T obj) лучше.

Но, давайте отойдем от абстрактных примеров с животными и используем пример с IQueryable, который, как вам известно наследуется IEnumerable. Как вы думаете, победит ли Say(IEnumerable enumerable) хотя бы раз?




static void Say<T>(T obj)
{
Console.WriteLine("Say<T> won!!!");
}

static void Say(IEnumerable enumerable)
{
Console.WriteLine("IEnumerable won!!!");
}

static void Main(string[] args)
{
int[] a = {1, 2, 3};

IQueryable q = a.AsQueryable();

Say(a);
Say(q);
}


Нет. Не победит.
Say won!!!
Say won!!!


Именно это и было причиной моей бессонной ночи, о которой я упомянул вначале статьи. Но, вернемся к нашим барана…, то есть животным.

Следующие тесты показывают, что поведение компилятора можно все-таки контролировать путем явного приведение типа к интерфейсу или базовому классу.
Разрешение имен – это тема очень интересная. Только, к сожалению, бой между интеллектом программиста и компилятором всегда выиграет компилятор.

А победа на войне – за нами!

По мотивам книги «More Effective C# 50 Specific Ways to Improve Your C#»
Глава: Item 7. Do Not Create Generic Specialization on Base Classes or
Interfaces

четверг, 26 ноября 2009 г.

10 простых правил, чтобы лучше писать приложения на ASP.NET

При создании приложений на ASP.NET, мы, как правило, тратим множество усилий на проектирование и изоляцию различных слоев приложения, с упором на предметную область и тому подобное. Однако, как только мы приближаемся к программированию пользовательского интерфейса, код становится более грязным. Я могу объяснить это тем, что чем ближе мы к пользовательскому интерфейсу, тем меньше шансов того, что код используется повторно, или, по крайней мере, предназначен для многократного использования. Несмотря на то, что это может быть правдой (что код не предназначен для повторного использования), UI-код должен быть чистыми и легким для сопровождения.

Код UI должен использовать такие принципы ООП, как наследование и инкапсуляция, ровно как код любого другого слоя. Кроме того, некоторые принципы SOLID, могут и должны быть применены при разработке наших юзер-контролов и веб-страниц. Так как ASP.NET WebForms по-прежнему широко используется, и я полагаю, что так продлится еще долгое время, в этой статье, я предлагаю тот набор правил для программирования и проектирования, который я использую в своих ASP.NET проектах. Надеюсь, что следующие правила помогут вам сделать ваш UI код более чистым и и облегчат его поддержку в дальнейшем.

1. Используйте собственный базовый класс для классов пользовательского интерфейса


Начинайте проект со создания крайней мере двух базовых классов BasePage и BaseUserControl и наследовать от них все ваши веб-формы и юзер-контролы. Это считается хорошим стилем. Причина в том, что у вас непременно возникнет необходимость реализовать общий код для всех или части ваших веб-форм или юзер-контролов.
К примеру, все страницы вашего сайта должны иметь название и, в дальнейшем, некоторым мета-теги. Добавление свойства PageID, а также метода, который вернет название страницы и ее мета-теги будет довольно легко достигнуть при помощи наследования, а поддерживать код, очевидно, станет намного легче.
Другим распространенным случаем является необходимость показать / скрыть элементы веб-формы, в зависимости от определенного условия. Добавив виртуальный метод в базовый класс, который вызывается при возникновении события OnPreRender, вы сможете реализовать механизм скрытия и отображения элементов веб-формы. И весь этот код будет удобно расположен в одном методе и работать для всех классов-наследников, пока вы не переопределите его. Следующий код демонстрирует все вышесказанное, используя дизайн-паттерн "Шаблонный метод"

Пример:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Collections.Specialized;
using System.Data;
public class BasePage : System.Web.UI.Page
{
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
DoEnableDisableControls();
SetTitleFromCMS();
SetMetaTagsFromCMS();
}
public string PageID
{
get;
set;
}
/// &lt;summary&gt;
/// Overwrite this method to enable or disable controls
/// depending on the state of the page.
/// &lt;/summary&gt;
protected virtual void DoEnableDisableControls()
{
}

private void SetTitleFromCMS()
{
if (String.IsNullOrEmpty(PageID))
return;
//Queries the CMS for the page title
this.Title = "Whatever we got from the CMS";
}
private void SetMetaTagsFromCMS()
{
//Queries the CMS for the MetaTags
//add the meta tags
}
}


Странице, которая наследуется от базового класса, будет просто необходимо установить свойство PageID – и она будет иметь заголовок и мета-теги (обратите внимание на возможность задать PageID непосредственно в директивах .aspx файла страницы).

<% @ Page Language = "C #" PageID = "DefaultPage" AutoEventWireup = "True" CodeBehind = "Default.aspx.cs" Inherits = "% ExtRefWebApp._Default">


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

2. Скрывайте переменные сессии


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

  • Переменные сессии широко используется в веб-приложениях. Они очень полезны, но мы не должны забывать, что переменная Session, с точки зрения ООП – это ничто иное, как глобальная переменная.
  • При отладке или поддержке веб-приложений, мы часто сталкиваемся с участками кода, которые используют значения, хранящиеся в Session, и нам необходимо проверить, где эти значения были установлены. И это не легко выяснить.
  • Session может содержать любой тип. Если мы заменим тип значения, хранящегося в определенном ключе Session, нам придется проверить весь код и изменить все приведения типов (мы делаем это с помощью поиска/замены текста в коде).
  • Конкретного ключа Session может не существовать, либо он может иметь значение null. Не существует способа для того, чтобы получить по умолчанию для несуществующего ключа Session и, таким образом, всякий раз, когда мы используем переменную сессии, мы должны проверить ее значение (на null) перед использованием.

Лучший способ избежать этих проблем – создать набор классов, по средствам установки свойств которых мы и будем изменять значения Session. При помощи функции Visual Studio "Find all references"/"Find Usages", из контекстного меню при клике на сеттере свойства, вы найдете все объекты, которые устанавливают переменную сеанса. С другой стороны, если мы изменим тип значения в переменной Session, то код, использующий старый тип просто выдаст ошибку компиляции, следовательно, эти ошибки будет очень легко найти и сделать все изменения или оценить объем работы, необходимой, чтобы сделать изменения. Это ужесточает контроль типов и делает наш код более безопасным.
Доступ к значениям Session должен быть инкапсулирован в разных классах, и если их будет слишком много, то, желательно, чтобы они принадлежали одному пространству имен и находились в одной папке, это облегчит их поддержку.

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

3. Не создавайте глубокой иерархии UI-контролов


Пользовательские элементы управления в ASP.NET являются очень хорошим средством для повторного использования кода пользовательского интерфейса. Однако, когда мы начинаем вставлять одни юзер-конролы в другие, то, в конце концов мы получаем бесконечное дерево вложенных друг в друга элементов управления . Это превращает поддержку приложения в настоящий ад.

Обычно, это хорошая привычка ограничить дерево максимум на 2 или 3 уровня. Страница содержит элементы управления, которые могут содержать другие элементы управления. Но внутренние элементы должны содержать только базовые элементы управления .NET Framework (или элементы управления веб-сервером). И даже на одном уровне, мы должны ограничивать число юзер-контролов, включенных в родительский юзер-контрол. В том смысле, что создание одного большого юзер-контрола, который содержит множество внутренних юзер-контролов – это не всегда хорошая идея. Имейте в виду, что сама страница может содержать столько контролов, сколько вы пожелаете: я заметил, что очень часто разработчики не используют базовые контролы ASP.NET, а встраивают все в пользовательский Сверх-элемент управления, который в свою очередь, содержит дочерние элементы. Зачем нам добавлять этот уровень косвенности и сложности? Страница – это прекрасный контейнер для элементов управления.

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


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

  • Повторное использование: если мы знаем, что этот контрол будет использоваться в другом месте, надо вставлять его в пользовательский элемент управления,
  • Правило комплексности: Иногда лучше отделить некоторые части пользовательского интерфейса, потому что мы хотим инкапсулировать некоторые сложные участки кода в отдельный класс и файл. Это не что иное как принцип эдиной ответственности, применяющийся для компонентов пользовательского интерфейса.

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

5. Используйте события для обмена данными между юзер-контролами и их контейнерами


Использование свойств пользовательских элементов управления, чтобы сообщить о своем состоянии своему контейнеру – это типичная схема, которую мы видим в веб-разработке. В большинстве случаев разработчики устанавливают переменную сессии, когда что-то случается в пользовательском элементе управления, и контейнер (будь то страница или другой пользовательский элемент управления) проверяет значение переменной сессии для того, чтобы определить, действительно ли что-то случилось или нет. Это обычно делается в событие "OnLoad". Наверное, вы заметили, что очень быстро, мы, в итоге, получаем довольно беспорядочный код в этом обработчике событий.
Во-первых, мы должны гарантировать, что код, который устанавливает переменную сессии выполняется перед кодом, который проверяет ее значение. Кроме того, мы должны гарантировать, что значение переменной сбрасывается: в противном случае, в следующий раз, когда мы попадаем на ту же страницу, мы можем спровоцировать событие, без всяких на то причин.
Один элегантный способ уведомить об изменение статуса одного элемента другому – это создать событие, на которое можно будет подписаться (прим.: public event в C#). Есть много преимуществ такого подхода:

  • Чистый код, который реагирует на конкретные события с внутренних контролов,
  • Один и тот же механизм может быть использован для стольких контролов, сколько мы пожелаем,
  • Мы будем уверены в том, что наш контрол будет извещен только в том случае, если событие действительно произошло; в то время как использование переменных сессии добавляет неопределенности, поскольку мы не знаем, какой код выполняется первым: код, который установил значение Session, или код, который проверяет его.
  • Мы не должны заботиться о переменной сессии и сбрасывать его после событий, поскольку мы больше не используем их.
  • Мы не используем значения Session просто для того, чтобы поставить/считать флаг (а это экономия памяти и уважение основных принципов ООП).


6. Избегайте встраивания C#/VB кода в файлах ASPX


Лучшее место, чтобы добавить программный код в веб-формы – это code-behind файл. Внедрение кода в файлы ASPX делает его грязным и очень трудно поддерживаемым, как в старые времена ASP.

7. Используйте декларативный стиль программирования как можно чаще


Если вам необходимо настроить пользовательский или любой другой серверный элемент управления – сделайте это по средствам свойств в ASPX-файле вместо code-behind. Конечно же, бывают ситуации, когда у нас нет выбора, где настроить элемент управления (условные значения, например), но, в общем, это делает код HTML более четким и облегчает его поддержку, поскольку мы знаем многое о контроле, просто взглянув на один ASPX файл (и нет необходимости смотреть в code-behind файл).

8. Будьте осторожны при использовании статических переменных


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

Один пример я видел в прошлом, проект хранил пользовательскую информацию в виде статического словаря (между прочим, это может быть любой тип коллекции). При нагрузочном тестировании, использование памяти приложения выросло до 12 ГБ на пару сотен пользователей. Так как словарь хранился в статической переменной, ее размер увеличивался при добавлении новых пользователей и, в конце концов, мы получили исключение "Out of memory".

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

9. Рассматривайте производительность с точки зрения клиента


Когда мы говорим о производительности, то часто фокусируемся только на код, который выполняется на сервере, и пренебрегаем пропускной способностью сети и количеством загрузок страницы. Хотя процессор и память дешевле увеличения пропускной способности сети, с другой стороны, пропускная способность является более широкой при работе в контролируемой среде интранет, но мы не имеем никакого контроля на имеющуюся пропускную способность для пользователей веб-сайта в сети Интернет.

Существуют различные средства и способы, чтобы сделать производительность страницы лучше на клиентской стороне (хорошей отправной точкой, будет эта статья: http://www.codeproject.com/KB/aspnet/PeformanceAspnet.aspx).

В коде на серверной стороне, мы не должны допускать чрезмерного использования ViewState. С одной стороны, хранение дерева объектов в ViewState может показаться полезной и более эффективным техникой, чем сохранять его в сессии, ведь объект не будет висеть на неопределенное время в памяти. Но, эффективность будет еще хуже, поскольку на самом деле ваше дерево объектов будет сериализовано и передано как часть ответа в браузер клиента. И когда клиент отправит форму, сериализованный объект отправляется обратно на сервер.

Другой проблемой является количество Javascript файлов, которые загружаются вместе со страницей. Мы должны объединить их с помощью ScriptManager (см. http://bellouti.wordpress.com/2008/09/14/combining-javascript-files-with-ajax-toolkit-library/ ).

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

10. Используйте кэширование, где это только возможно


Пользовательские элементы управления можно кэшировать с помощью директивы OutputCache. При создании пользовательских элементов управления, которые должны показываться содержание, взятое из БД или внешних источников, следует рассмотреть возможность использования этой директивы.
Мы также можем кэшировать данные в статических членах или пользования объектами кэширования EnterpriseLibrary. Если мы сделаем это, мы должны гарантировать, что мы создаем кэш в форме, ближайшей к той, что будет выводиться в интерфейсе пользователя. Например, если вы получаете XML из базы данных и трансформируете его по правилам XSLT в HTML, который и будет отображаться, то кэшировать следует результирующий HTML, а не XML. Кэширование HTML позволит избежать повторных преобразований XML в следующий раз, когда будет запрошена страница.



Автор статьи: Samir Bellouti
Оригинал: 10 simple rules to write better ASP.NET applications
Перевод: Дмитрий Жарий

воскресенье, 16 августа 2009 г.

C# и Java – это языки будних дней, а Python и Ruby для выходных?

Результаты интересного эксперимента опубликовал Ден Лоренц в своем блоге. Используя официальный дамп базы данных сайта StackOverflow, он подсчитал с какой интенсивностью задаться вопросы по C#, Java, Ruby и Python.

Как оказалось, на протяжении всей рабочей недели, лидируют C# и Java, в то время, как на выходных активность идет на спад. В тоже время, на выходных активность вопросов по Ruby и Python возрастает.

Прочитать пост можно здесь:
StackOverflow Experiment Results

вторник, 21 июля 2009 г.

Perl, C# и регулярные выражения

Казалось бы, Perl и C# -- языки очень разные, но как оказывается, C# поддерживает стандарт де-факто регулярных выражений – Перл совместимые регулярные выражения.

Конечно же, для Perl регулярные выражения родные, и сними намного удобней работать именно в Перле, но, для того чтобы регулярные выражения заработали в C# -- достаточно просто взять и скопировать их из перлового скрипта.

Единственное что мне пока что не нравиться – это то, что регекспы в C# компилируется в момент выполнения. Таким образом, можно допустить ошибку где-то в тридесятом условии, в тридевятом вызове метода, и тогда вылезет исключение. В Перл регекспы проверяться в момент компиляции скрипта и банальные опечатки вылезают сразу же.

Именно поэтому, я разрабатываю и тестирую регекспы в Перл, и лишь потом переношу их в C#. Это получается быстрей удобней и стабильней.

Напоследок хочу продемонстрировать на простом примере, что уже сейчас код на C# выглядит ненамного избыточней, чем его аналог на Perl.

Следующий код выводит дату и время из строчек массива variants:

#!perl
my @variants =
(
q~^92(28/10/2002)**&[19:10:50]@Comment: Text String"~,
q~^-06/02/2002*$21:55:45~,
q~^*13/08/2002*DX145&[22:50:20]@Comment: Text String~,
);

foreach my $variant ( @variants ) {

if ( $variant =~ m!([0-3][0-9]/[0-1][0-9]/[1-2]\d\d\d).+?
                       ([0-2][0-9]:[0-5][0-9]:[0-5][0-9])!x )
{
print "Date: $1; Time: $2\n";
}
else {
print "Failed to parse string: $variant\n";
}
}


И пример на C#:

using System;
using System.Text.RegularExpressions;

namespace RegexTest
{
class Program
{
static void Main(string[] args)
{
string[] variants = {
@"^92(28/10/2002)**&[19:10:50]@Comment: Text String""",
@"^-06/02/2002*$21:55:45",
@"^*13/08/2002*DX145&[22:50:20]@Comment: Text String",
};
Regex veriantRegex = new Regex(@"(?<date>[0-3][0-9]/[0-1][0-9]/[1-2]\d\d\d).+?" +
@"(?<time>[0-2][0-9]:[0-5][0-9]:[0-5][0-9])" );
foreach (var variant in variants)
{
Match match = Match.Empty;
if ((match = veriantRegex.Match(variant)) != Match.Empty)
{
Console.WriteLine("Date: {0}; Time: {1}", match.Groups["date"].Value,
match.Groups["time"].Value);
}
else
{
Console.WriteLine("Failed to parse string: {0}", variant);
}
}
}
}
}


Output:

Date: 28/10/2002; Time: 19:10:50
Date: 06/02/2002; Time: 21:55:45
Date: 13/08/2002; Time: 22:50:20

понедельник, 20 апреля 2009 г.

Красота string.Empty или скорость“”?

Для разработки небольшого парсера, решил вынести все значения ключевых слов в конструкцию switch. Так как пустая строка также предусматривается, то решил воспользоваться гламурным string.Empty в кейсе:

static void Main(string[] args)
{
string token = " ";
switch (token.Trim())
{
case string.Empty:

break;
}
}


Я очень удивился, когда компилятор выдал ошибку, что в строке
case string.Empty:
требуется констанотное вырожение.
Лезем в Object Browser и смотрим декларацию string.Empty:

public static readonly string Empty в классе String.

Почему не константа?! Может быть, есть какое-то сверх объяснение, связанное со сверх оптимизациями и прочей магией?
Погуглив на это тему, обнаружил замечательную статью:
string.Empty Example and Issues in C#
Оказывается, что даже при использовании в конструкции if, сравнение со string.Empty работает в два раза медленнее, чем с константной пустой строкой "":

=== string.Empty benchmarked code ===

if (string.Empty == null)
{
throw new Exception();
}

=== Constant string benchmarked code ===

if ("" == null)
{
throw new Exception();
}

=== Benchmark results ===
Second loop was completely compiled out before runtime.
1000000000 iterations.

string.Empty loop: 637 ms
"" loop: 319 ms [faster]


Я не отказываюсь от использования string.Empty, но теперь призадумаюсь о том, стоит ли использовать такую запись пустой строки на конкретном участке кода.

понедельник, 13 апреля 2009 г.

Строки в C# и внутренний пул

Я не знаю, почему это людей так беспокоит вопрос о том, где формируются строки с C# и так радует ответ, что они формируются во внутреннем пуле(Intern pool). Но, видимо это очень важно, так как такие вопросы присутствуют в большинстве статей по подготовки к собеседованию по C# и такой вопрос, я сам на него отвечал, есть в тесте BrainBench.

Мне, честно говоря, было пофигу, где эти строки формируются и хранятся, главное, что это все работает. Я воспринимал это как аксиому, которую, точнее, нужно сформулировать так: «Все строковые литералы компилятор C# помещает во внутренний пул».

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

string name = textReader.ReadLine();
switch (name)
{
case "Bob":
Console.WriteLine("Hello, Bob!");
break;
case "Vasya":
Console.WriteLine("Privet, Vasya!");
break;
default:
Console.WriteLine("O_o Vasya!");
break;
}


Ведь в переменной name содержится строка, прочитанная из файла в момент выполнения, так как же эти строковые кейсы работают? Они же, по идеи, должны сравнивать ссылки. А чтобы моя переменная name совпала хотя бы с одним этим кейсом, то она тоже должна быть интернирована (помещена в пул), как и строковые литералы.

Оказывается, что да. И в каком месте это происходит, я пока еще не знаю. Но, строковые литералы помещаются в пул в момент компиляции, а новые, динамические строки взаимодействуют с этим пулом в момент выполнения. Получается, что каждая новая строка сравнивается со всеми уже существующими, что может влиять на производительность кода.

Я наивно пытался похачить это дело, но все-таки ничего не получилось:

using (TextReader tr = File.OpenText(@"C:\temp\strings.txt "))
{
string string1 = tr.ReadLine(); // This is a string
string string2 = tr.ReadLine(); // This is a string
string string3 = tr.ReadLine(); // This is another string

Console.WriteLine("First try:");
Console.WriteLine(" string1={0}\n string2={1}\n string3={2}", string1.GetHashCode(),
string2.GetHashCode(),
string3.GetHashCode());
StringBuilder sb = new StringBuilder();
string internPool = "Intern Pool";
sb.Append("Intern ").Append("Pool");
Console.WriteLine("Second try:\n string1={0}\n string2={1}", internPool.GetHashCode(),
sb.GetHashCode());

Console.WriteLine("Third try:\n string1={0}\n string2={1}", internPool.GetHashCode(),
sb.ToString().GetHashCode());

}


Текстовый файл:

This is a string
This is a string
This is another string


Вывод первой попытки, как не странно string1 и string2 равны:
First try:
string1=2035671784
string2=2035671784
string3=164170943


Вывод второй попытки! Ага все таки не равны! Но, на самом деле и типы тут разные:
Second try:
string1=-1024490162
string2=58225482


Вывод третий попытки. Приведем-ка это это добро .ToString. :( Все таки ссылки равны:
Third try:
string1=-1024490162
string2=-1024490162



Upd. Вот что значит дописывать код в 2 часа ночи :) На самом деле, ссылки не равны, а .GetHashCode это не правильный способ. Спасибо, Alex, за комментарий.

Если добавить следующие строки кода, то выясняется, что ссылки действительно не равны:

Console.WriteLine("Ref Eq string1 and string2 = {0}", object.ReferenceEquals(string1, string2));
Console.WriteLine("Ref Eq string2 and string3 = {0}", object.ReferenceEquals(string2, string3));
// и...
Console.WriteLine("Ref Eq internPool and sb.ToString() = {0}", object.ReferenceEquals(internPool, sb.ToString()));


Результат:

Ref Eq string1 and string2 = False
Ref Eq string2 and string3 = False

Ref Eq internPool and sb.ToString() = False

вторник, 7 апреля 2009 г.

Экономим байты исходного кода. Реализация алгоритма QSort

Однажды, когда я проходил трейнинг по C#, нам было задано домашние задание, одним из пунктов которого было реализовать алгоритм быстрой сортировки(QSort) на C#. По достаточно хорошему описанию алгоритма на страницах Википедии, я все-таки реализовал алгоритм по-своему, но решил продолжить поиск наиболее «короткого» решении. И я его нашел. Следующий код написан на языке Perl, и это то, к чему нужно стремиться:

sub qsort {
return () if !@_;
return (qsort(grep { $_ < $_[0] } (@_)[1..$#_]), $_[0],
qsort(grep { $_ >= $_[0] } (@_)[1..$#_]));
}


Превосходно! Реализация алгоритма наглядна и прекрасно запоминается. Я попытался переписать исходный код с Perl на C#, и вот что у меня получилось:

public static List<T> QSort<T>(List<T> sourceArray)
where T : IComparable<T>
{
if ( sourceArray.Count == 0 )
{
return new List<T>();
}
T firstItem = sourceArray[0];
sourceArray.RemoveAt(0);

List<T> greaterList = new List<T>();
List<T> lessList = new List<T>();
List<T> sortedArray = new List<T>();


foreach (T item in sourceArray)
{
if (item.CompareTo(firstItem) < 0) lessList.Add(item);
}

foreach (T item in sourceArray)
{
if (item.CompareTo(firstItem) >= 0) greaterList.Add(item);
}

sortedArray.AddRange(QSort(lessList));
sortedArray.Add(firstItem);
sortedArray.AddRange(QSort(greaterList));

return sortedArray;
}


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

public static IEnumerable<T> QuickSort<T>(this IEnumerable<T> list)
where T : IComparable
{
if (!list.Any())
{
return Enumerable.Empty<T>();
}
var pivot = list.First();
var smaller = list.Where(item => item.CompareTo(pivot) <= 0).QuickSort();
var larger = list.Where(item => item.CompareTo(pivot) > 0).QuickSort();

return smaller.Concat(new[] {pivot}).Concat(larger);
}


Кто короче?! ;)

суббота, 4 апреля 2009 г.

.NET, NHibernate, Castle ActiveRecord и генераторы кода. В поисках истины

Я работал раньше с ORM’ами на других языках программирования, и должен сказать, что я, как человек ленивый и стремящейся всеми возможными способами упростить свою работу, уже прочно привык к их использованию. Так что, вместо того, чтобы писать деревянные и тривиальные запросы для CRUD-операций и кучу JOIN’ов для реализации связей между таблицами, я предпочитаю заюзать хороший ORM и не париться по этому поводу.
Так как проект пишется на ASP.NET с возможной поддержкой Mono, то мне было необходимо, чтобы этот ORM работал с MySQL, чтобы был хороший генератор кода для классов записей, и чтобы он был как можно проще для изучения. Ну, не хочу я месяц тратить на изучение какой-нибудь технологии, без которой, я в принципе, могу и обойтись.

NHibernate


Первое, что мне бросилось в глаза для .NET – это NHibernate (рус. http://nhibernate.ru/). Очень мощный ORM, используемый достаточно большим количеством людей. На то, чтобы разобраться в азах этой штуковины, я потратил целый вечер, и понял, что это вещица это действительно мощная, но не такая уж и простая и содержит кучу нюансов. Это я понял, пролистав «купленную» книгу NHibernate In Action и просмотрев несколько отрывков из скринкастов по NHibernate. Слишком сложно. В топку :(

ActiveRecord


Бороздя необъятные просторы Интернета, я все-таки нашел то, что мне нужно. Это Castle ActiveRecord. Хорошо, что он построен на базе NHibernate, так что мне не пройдется отказываться от функционала NHibernate. Но, это проще чем NHibernate! И это то, что нужно для моего проекта. Ура! Меня не пугает то, что последний релиз (точнее релиз кандидат) был полтора года назад, я смотрю на то, что в Сети есть много позитивных отзывов по поводу Castle ActiveRecord. Кроме того, я немного работал с ActiveRecord в Ruby, и уверен, что для меня не составит труда быстро изучить, как использовать реализацию ActiveRecord на C#.

Генераторы кода


Уйму времени потратил на то, чтобы найти тулзу, которая бы сгенерировала мне код для моих ActiveRecord классов. Ситуацию осложняло то, что мне нужно было сгенерировать классы, по готовой БД, под управлением MySQL. Перепробовал кучу генераторов кода, но одни отказывались работать с MySQL, другие отказывались работать с MySQL. Хотел уже впасть в панику, и перегнать БД из MySQL в SQL Server, и уже от туда сгенерировать нужные классы. Но, проблема разрешилась. Нашел вот это:
Caragen Tool - the Castle Active Record Autonomous GENerator tool, которое прекрасно поддерживает работу с MySQL.

И сейчас, довольный тем, что у меня есть готовые классы c уже описанными полями, пошел править связи между таблицами.

вторник, 24 марта 2009 г.

Крик души ;)

public static Testplan ParseFile(string filePath)
{
ApplicationClass excel = new ApplicationClass();
excel.Visible = true;
excel.Workbooks.Open(filePath, false, false, null, null, null,
true, null, null, null, null, null, null,
null, null);

// А все таки фигово, что в 3-м Сишарпе нет именованных аргументов
// и аргументов по умолчанию. Ждем-с 4-го Сишарпа :(

return new Testplan(null, null, null);
}


Тоже самое, только на Perl:

my $Book = $Excel->Workbooks->Open( {
FileName => $FILE_NAME,
UpdateLinks => 0,
ReadOnly => 1,
IgnoreReadOnlyRecommended => 1,
}
) or die("Cannot open file $FILE_NAME");


UPD:

На самом деле, пропущенные параметры нужно заполнять при помощи значения Missing.Value, которое описано в System.Reflection. В противном случае (если использовать null), можно нарваться на Exception.



excel.Workbooks.Open(filePath, false, false, Missing.Value, Missing.Value, Missing.Value,
true, Missing.Value, Missing.Value, Missing.Value, Missing.Value,
Missing.Value, Missing.Value, Missing.Value, Missing.Value);

вторник, 17 марта 2009 г.

Культура красивого кода на C#

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

Aviva Solutions C# 3.0 Coding Guidelines (pdf)
Aviva Solutions C# 3.0 Coding Guidelines Quick Reference Guide (pdf)

воскресенье, 15 марта 2009 г.

sealed классы работают быстрее... не намного, но быстрее!

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

В следующем примере я создал 10 виртуальных методов в базовом классе BaseClass. И эти функции переопределяются в классах DerSealedClass и DerNotSealedClass. DerSealedClass – это sealed класс, а DerNotSealedClass – нет.
Каждую итерацию цикла, вызываются все эти 10 методов у обоих классов.
Вот результаты теста для 100 000 000 итераций. Чем меньше времени потрачено – тем лучше!

Iterations: 100000000;
DerNotSealedClass: 357 ms;
DerSealedClass: 345 ms;

Как видите, разрыв небольшой, но все таки sealed классы работают быстрее.

Вот код теста:
using System;
using System.Diagnostics;

namespace TestTime
{
class Program
{

public class BaseClass
{
public virtual int Op0() { return 1; }
public virtual int Op1() { return 1; }
public virtual int Op2() { return 1; }
public virtual int Op3() { return 1; }
public virtual int Op4() { return 1; }
public virtual int Op5() { return 1; }
public virtual int Op6() { return 1; }
public virtual int Op7() { return 1; }
public virtual int Op8() { return 1; }
public virtual int Op9() { return 1; }
}

public sealed class DerSealedClass : BaseClass
{
public override int Op0() { return 2; }
public override int Op1() { return 2; }
public override int Op2() { return 2; }
public override int Op3() { return 2; }
public override int Op4() { return 2; }
public override int Op5() { return 2; }
public override int Op6() { return 2; }
public override int Op7() { return 2; }
public override int Op8() { return 2; }
public override int Op9() { return 2; }
}


public class DerNotSealedClass : BaseClass
{
public override int Op0() { return 3; }
public override int Op1() { return 3; }
public override int Op2() { return 3; }
public override int Op3() { return 3; }
public override int Op4() { return 3; }
public override int Op5() { return 3; }
public override int Op6() { return 3; }
public override int Op7() { return 3; }
public override int Op8() { return 3; }
public override int Op9() { return 3; }
}

private static void perfTest()
{

const int iterations = 100000000;

Stopwatch sw1;
Stopwatch sw2;

DerNotSealedClass notSealed = new DerNotSealedClass();
DerSealedClass Sealed = new DerSealedClass();

sw1 = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
{
notSealed.Op0();
notSealed.Op1();
notSealed.Op2();
notSealed.Op3();
notSealed.Op4();
notSealed.Op5();
notSealed.Op6();
notSealed.Op7();
notSealed.Op8();
notSealed.Op9();
}
sw1.Stop();

sw2 = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
{
Sealed.Op0();
Sealed.Op1();
Sealed.Op2();
Sealed.Op3();
Sealed.Op4();
Sealed.Op5();
Sealed.Op6();
Sealed.Op7();
Sealed.Op8();
Sealed.Op9();
}
sw2.Stop();
Console.WriteLine("Iterations: {0};", iterations);
Console.WriteLine("DerNotSealedClass: {0} ms;", sw1.Elapsed.Milliseconds);
Console.WriteLine("DerSealedClass: {0} ms;", sw2.Elapsed.Milliseconds);
}

static void Main(string[] args)
{
perfTest();
}
}
}
 

.NET ate my MOSK;. Powered By Blogger © 2009 Bombeli | Theme Design: ooruc