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

Time scope в Ninject

Постановка задачи

Есть некий data-provider, собирающий информацию из разных источников (локальная сеть, файловая система, ресурсы интернета) и предоставляющий программе некоторую сводную информацию. При этом, сбор и подготовка информации занимает 1-5 секунд, а обращение к провайдеру происходит несколько раз в секунду. Однако, исходная информация обновляется достаточно медленно и небольшое устаревание (в пределах нескольких часов) вполне допустимо. Очевидное решение задачи - это кэш. Кэш - это хорошо, это правильно, но хотелось бы так, что бы попроще - 1 строчка кода.. хм, ну максимум две..

Ninject решение

И тут я вспомнил, что мой провайдер лежит в биндингах Ninject-а. Эврика! Достаточно добавить к биндингу "волшебную"строчку .InSingletonScope() и будет "счастье". При запуске программы собираются данные и оседают в синглтон-объекте до конца жизни программы. В момент следующего запуска, данные снова собираются свежие. И, все бы хорошо, если бы не один нюанс - провайдер захотелось поместить в службу на сервере, которая не перезапускается по пол года. Ок. Хочется тот же синглтон, но так, что бы он все-таки пересоздавался.. иногда, ну, например, раз в два часа. Для реализации такого функционала в Ninject есть понятие custom scope - метод .InScope(...). В качестве параметра данный метод принимает функцию, которая, в свою очередь, должна вернуть произвольный объект. Именно на этот объект и будет ориентироваться система - если он изменится (будет отличаться от того, который функция вернула в прошлый раз), то наш провайдер будет пересоздан. Осталось понять, где взять такой объект, который будет пересоздаваться через нужный временной интервал. Создавать громоздкую систему со статическими hash-таблицами совершенно не хотелось (тогда уж лучше сделать кэш). И тут пришла в голову идея - что если в качестве такого объекта использовать специальное число, полученное следующим способом:
var diff = DateTime.Now.Ticks / time.Ticks;
Где time - это нужный интервал времени (TimeSpan). И, все бы хорошо, но! Ninject, как выяснилось путем простого чтения исходных кодов (и как это я сразу не догадался туда заглянуть), сравнивает объект по ссылке, т. е. с помощью метода object.ReferenceEquals(...), а не "по-человечески" с помощью object.Equals(...), как я наивно ожидал. В итоге, простые типы в качестве специального объекта не работают.

"Магия" строк

Вот тут-то я и решил использовать "магию". Итак, мне нужен объект, который будет правильно сравниваться по ReferenceEquals и, при этом, будет вычисляться "на лету", что бы не хранить его ни в какой статике и не думать о накапливаемом "мусоре". Хм. Вообще-то такое сделать нельзя - без статического хранилища эта задача не решается. Но! можно использовать строки. Дело в том, что внутри .Net в строках уже заложено статическое хранилище и оно поддерживается системой. А, значит, нам достаточно этим воспользоваться. Добавим еще одну строчку кода и обернем это в удобную функцию, которую можно будет использовать многократно:
object GetTimeScope( TimeSpan time )
{
   var diff = DateTime.Now.Ticks / time.Ticks;
   return string.Intern( diff.ToString() );
}
Главная "магия" тут в слове Intern - именно оно и даст нам правильную работу ReferenceEquals. Ну а использовать этот метод совсем просто:
kernel.Bind<TestDataObject>().ToSelf().InScope( c => GetTimeScope( TimeSpan.FromHours( 2 ) ) );
Удачного программирования.

четверг, 17 сентября 2015 г.

Исправить или переписать. Ловушки нашего подсознания

Солнышко уже скрылось, а я все "пилю" один и тот же кусок кода. А хотел-то всего лишь исправить мелкую ошибку. Однако, исправив одно, обнаружил, что отвалилось другое, а там, в свою очередь, уже и так куча "костылей-распорок", так как код этот правится не в первый раз. Вот в такие моменты и начинаешь думать "а не переписать ли весь этот блок с нуля - так, что бы без распорок, без костылей...". На что пессимист в глубине сознания начинает возражать "А сколько на это времени уйдет? Код-то не маленький. А, может, проще-таки еще одну заплаточку?". Так как же поступить? Переписывать или править дальше? Как правильно оценить время? На самом деле, оценивать объем кода по базовым факторам мы все умеем - смотрим на количество строк, прикидываем сложность, сопоставляем с тем, сколько обычно времени уходит на такой код. Все это понятно, но есть факторы, сильно искажающие нашу оценку - факторы, действующие на подсознательном уровне и приводящие к неадекватным решениям.

Свой или чужой код

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

суббота, 12 сентября 2015 г.

Упрощаем код и пишем красиво

Работая в команде, мне часто приходится читать чужой код. И, периодически я вижу простые вещи написанные весьма громоздко и совершенно нечитаемо. Например, много повторяющихся блоков или, еще хуже, лишние операции. Такой код хорош, если ваша цель сделать так, что бы никто не разобрался, однако, в большинстве случаев, такая цель перед нами не стоит.
bool a = SomeCondition();
/* Как упростить следующий код? */
bool b;
if( a.ToString().Length == 5 )
{
   b = true;
} else
{
   b = false;
}
Этот небольшой отрывок я привел отчасти ради шутки, а отчасти для того, что бы показать о чем идет речь. Разумеется, я не думаю, что кто-то реально так пишет. Но, в реальности наши задачи несколько сложнее и, по мере поступления новых вводных, мы часто перестаем видеть знакомые шаблоны и, как следствие, начинаем писать в стиле приведенного выше шуточного примера.

вторник, 20 мая 2014 г.

Проектируем стандартное приложение (Ninject+WinForm)

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

Используемый инструментарий

Для примера, я буду использовать Ninject в качестве DI и Windows Form, в качестве UI. Понятное дело, что это совершенно не принципиально, так как тонкости различных DI систем я тут не рассматриваю, а принципиально они все решают одни и те же задачи.

Постановка задачи

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

Простейшая структура

Для примера, создадим структуру, состоящую всего из трех классов.
// Тип транспорта
public class TransportType
{
   public int Id { get; set; }
   public string Caption { get; set; }
}
// Модель транспорта
public class TransportModel
{
   public int Id { get; set; }
   public string Caption { get; set; }
   public string Description { get; set; }
}
// Конкретная единица - транспортное средство
public class Transport
{
   public int Id { get; set; }
   public string Caption { get; set; }
   public TransportType Type { get; set; }
   public TransportModel Model { get; set; }
}
Естественно, я предполагаю, что каждый класс будет отмапирован на табличку в некотором репозитории/базе данных с использованием какого-нибудь ORM fraimwork-а. Однако, для данной статьи это не принципиально.

суббота, 17 мая 2014 г.

Data Binding на Linq

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

Предисловие

Давным-давно, когда я только начинал постигать хитрости языка C# (а, так же, технологии .Net в целом), основной рабочей технологией была Windows Forms, WPF еще не было даже в проекте, а технология web была развита крайне слабо и, как следствие, применима в весьма узком круге задач. Нарисовав свою первую формочку, я, разумеется, задумался, как отобразить в нее свои данные. В случае, если форма readonly, задача решается просто - нужно сделать простые присвоения и все. Но, что если данные должны редактироваться и присваиваться назад, в область данных по триггерам Changed. Прямой подход (присвоить свойство, подписаться на его изменения, считать свойство в триггере) я откинул сразу, как слишком громоздкий, очень плохо читаемый и крайне не удобный для изменений.

Первые шаги. Что предлагают авторы?

Разумеется, первое, что я сделал - это стал изучать мануал и штудировать форумы. Из мануала от компании Микрософт я понял, что технология WinForms предоставляет очень мощный, удобный и универсальный механизм биндинга данных. Копая глубже и подкрепляя знания форумами и личными экспериментами, я понял, что почти все "странности" этой системы реально можно обойти. Еще более глубокое изучение показало, что все особенности и странности, кажущиеся на первый взгляд явными багами, на самом деле являются фичами. А все сложности и неудобства механизма оправданы теми или иными "высшими целями". Проникнувшись этими самыми "высшими целями", я, не долго думая, набросал себе простенький движок биндингов на чистом рефлекшине, реализовав минимально-необходимый для меня функционал и забыл про эту проблему на несколько лет.

вторник, 17 сентября 2013 г.

Слияние двух выражений (Linq.Expression)

Постановка задачи

Работая с технологией Linq, я недавно уперся в следующую проблему. В рамках моей задачи, мне было необходимо выражение вида Expression<Func<T1,bool>>, которое я должен был передать в Linq-запрос. При этом, извне мне передали выражение вида Expression<Func<T2,bool>>. Кроме того, у меня есть преобразование вида Expression<Func<T1,T2>>. Если бы мы имели дело с простыми делегатами типа Func, то все решилось бы последовательным вызовом двух функций: f = x => f1( f2( x ) );. Однако, с выражениями, мы такой финт сделать не можем, а приведения выражений к делегатам методом Compile(), создадут нам анонимные классы и последующая свертка сведется, соответственно, к вызову методов этих классов. А это значит, что результирующее выражение уже можно будет использовать только, как делегат - полноценного дерева выражений (к примеру, для построения SQL запроса) уже не будет.

Решение

Что бы сохранить нормальную структуру наших выражений, будем использовать класс ExpressionVisitor, с помощью которого обойдем дерево первого выражения и вместо переменной типа T2 подставим выражение для T1=>T2. Для этого напишем класс-наследник ExpressionVisitor:
public class ReplacementVisitor : ExpressionVisitor
{
   readonly Expression fromExpr;
   readonly Expression toExpr;

   public ReplacementVisitor( Expression oldExpr, Expression newExpr )
   {
      fromExpr = oldExpr;
      toExpr = newExpr;
   }

   public override Expression Visit( Expression node )
   {
      if( node == fromExpr )
         return toExpr;
      return base.Visit( node );
   }
}
Используя этот класс, создадим статическую функцию, которая собственно и будет решать нашу задачу
public static class ExpressionHelper
{
   public static Expression<Func<TSrc, TRes>> Combine<TSrc, TMes, TRes>( this Expression<Func<TSrc, TMes>> firstExpr, Expression<Func<TMes, TRes>> secondExpr )
   {
      var param = Expression.Parameter( typeof( TSrc ), "x" );
      var visitor = new ReplacementVisitor( firstExpr.Parameters[0], param );
      var newParentBody = visitor.Visit( firstExpr.Body );
      visitor = new ReplacementVisitor( secondExpr.Parameters[0], newParentBody );
      var body = visitor.Visit( secondExpr.Body );
      return Expression.Lambda<Func<TSrc, TRes>>( body, param );
   }
}
Осталось только сказать, что класс ExpressionVisitor появился в .Net Framework 4.0. Если же вы используете более ранние версии .Net, то данный класс можно взять с сайта Microsoft. Например, отсюда http://msdn.microsoft.com/ru-ru/library/bb882521(v=vs.90).aspx.

воскресенье, 15 сентября 2013 г.

Использование Ninject в Windows Forms

На днях столкнулся с такой проблемой - как использовать Ninject совместно с дизайнером Windows Forms. Понятное дело, создать саму форму проблем нет. Для этого достаточно заменить строку new Form1() на kernel.Get<Form1>(). В частности, для главного окна программы, мы просто корректируем класс Program, заменяя строку Application.Run( new Form1() ); на:
var kernel = new StandardKernel( new MyInjectModule() );
Application.Run( kernel.Get<Form1>() );
Но что же делать с пользовательскими контролями и компонентами, размещенными в дизайнере? Ведь код для их создания генерируется автоматически. Тут нас выручит возможность произвести инъекцию в уже готовый экземпляр с помощью метода Inject, определенного в интерфейсе IKernel и специальный атрибут InjectAttribute, позволяющий делать инъекции в свойства и методы. Итак, предположим, мы создали наше окно через Ninject, указанным выше способом. В конструкторе нашего окна вызывается сгенерированный студией метод InitializeComponent, который отрабатывает код дизайнера, рекурсивно создавая контроли и компоненты, расположенные в окне. Далее, наша задача рекурсивно обойти все эти элементы, вызывая для каждого метод Inject. В общем-то, ничего сложного в этом нет. А, что бы было еще проще, я написал маленький класс-хелпер. Выглядит он следующим образом:
public static class NinjectHelper
{
 public static void InjectToComponent( this IKernel kernel, Component parent )
 {
  if( parent is Control )
   foreach( Control c in ((Control)parent).Controls )
   {
    kernel.Inject( c );
    kernel.InjectToComponent( c );
   }
  var cmpInfo = parent.GetType().GetField( "components", BindingFlags.NonPublic | BindingFlags.Instance );
  if( cmpInfo != null )
  {
   var cmp = cmpInfo.GetValue( parent ) as IContainer;
   if( cmp != null )
    foreach( Component c in cmp.Components )
    {
     kernel.Inject( c );
     kernel.InjectToComponent( c );
    }
  }
 }
}
Использовать его очень просто - для этого надо вызвать метод InjectToComponent сразу после метода InitializeComponent:
public Form1( IKernel kernel )
{
 InitializeComponent();
 kernel.InjectToComponent( this );
}
После этого мы можем получить инъекции в любом нашем подэлементе (контроле или компоненте) через атрибут InjectAttribute следующим образом:
[Inject]
public int A
{
 get { return a; }
 set
 {
  a = value;
  //do some things
 }
}
Или, аналогично, через метод:
[Inject]
public void SetA( int a )
{
 //do some things
}
P. S. Я уверен, что найдутся люди, которые скажут "Вай! Зачем такие сложности? Не проще положить kernel в статический класс и дергать его оттуда из любого места?". Не смотря, на очевидность ответа на этот вопрос, я все же его озвучу. Если вы пишете универсальные блоки кода, предполагая их использование из разных приложений, то вы не можете знать, где и как создается ядро kernel - ваш статический класс вполне может оказаться непроинициализированным. Если же вы пишете небольшое приложение, не предполагающее особой гибкости, то Ninject вам вообще не нужен - все можно получить с помощью статических хелперов.

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

Как узнать, какие версии Framework установлены на компьютере

На днях передо мной встала задача определить программно, какие версии .Net Framework установлены на машине клиента. Покопавшись в реестре, я нашел нужную мне информацию в "HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP". Однако, что бы правильно вытащить ее оттуда, пришлось немного повозиться и вылилось в следующий код:
public class NetVersions
{
   public static Collection<Version> InstalledDotNetVersions()
   {
      Collection<Version> versions = new Collection<Version>();
      RegistryKey NDPKey = Registry.LocalMachine.OpenSubKey( @"SOFTWARE\Microsoft\NET Framework Setup\NDP", true );
      if( NDPKey != null )
      {
         string[] subkeys = NDPKey.GetSubKeyNames();
         foreach( string subkey in subkeys )
         {
            GetDotNetVersion( NDPKey.OpenSubKey( subkey ), subkey, versions );
            GetDotNetVersion( NDPKey.OpenSubKey( subkey ).OpenSubKey( "Client" ), subkey, versions );
            GetDotNetVersion( NDPKey.OpenSubKey( subkey ).OpenSubKey( "Full" ), subkey, versions );
         }
      }
      return versions;
   }

   static void GetDotNetVersion( RegistryKey parentKey, string subVersionName, Collection<Version> versions )
   {
      if( parentKey != null )
      {
         string installed = Convert.ToString( parentKey.GetValue( "Install" ) );
         if( installed == "1" )
         {
            string version = Convert.ToString( parentKey.GetValue( "Version" ) );
            if( string.IsNullOrEmpty( version ) )
            {
               if( subVersionName.StartsWith( "v" ) )
                  version = subVersionName.Substring( 1 );
               else
                  version = subVersionName;
            }

            Version ver = new Version( version );

            if( !versions.Contains( ver ) )
               versions.Add( ver );
         }
      }
   }
}
Буду рад, если оно понадобится еще кому-нибудь.

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

Как проверить, что мы в дизайнере (DesignMode) в Windows Form

Задача

Разрабатывая элементы управления в дизайнере Windows Form, нам часто необходимо проверить в каком режиме открыли наш элемент. Ведь в реальном режиме у нас может быть сложная логика чтения информации из БД, подключения в web-службам, взаимодействия с какими-то сторонними компонентами и т. д.. На момент редактирования визуального представления, вся эта логика должна быть отключена. А, для этого, нам необходим какой-то признак, по которому мы сможем отличить дизайнер от реального запуска программы:
if( !DesignMode )
{
    //...
}

Решения

Для начала посмотрим на то, что нам предлагают разработчики от Микрософт. В классе Component имеется свойство DesignMode. "Эврика! Это именно то, что нужно", подумал я. А, прочитав описание, я уж было совсем расслабился и начал писать свой код. Однако, первое же использование моего контроля в дизайнере, ввело меня в недоумение и растерянность - DesignMode был false. Всегда! Покурив форумы, я понял, что это свойство становится true после какого-то там обряда инициализации. Далее шли рассуждения о том, в какой момент времени это свойство показывает правильное значение, а в какой неправильное.. В общем-то рассуждения весьма бредовые, потому что код "иногда работающий правильно" - это как часы которые стоят - показывают правильное время ровно 2 раза в сутки, но толку от этого никакого. Кроме того, как я уже говорил, свойство находится в классе Component, а значит, до него невозможно добраться из вспомогательных классов, не имеющих ссылку на Component, что приводит к ограничениям в рефакторинге. А это значит, что свойство не просто бесполезно, но и вредно.
Продолжая курить форумы, я случайно наткнулся на класс LicenseManager. С его помощью проверку можно осуществить следующим образом:
if( LicenseManager.UsageMode != LicenseUsageMode.Designtime )
{
    //...
}
Однако, тут есть один маленький неприятный нюанс - этот класс писали те же "руки", что и предыдущий, а значит, работает все так же "глючно" и, в самый неожиданный момент, свойство UsageMode может выдать Runtime, не смотря на то, что мы находимся в дизайнере.
Еще одно интересное решение - это проверить название процесса:
if( Process.GetCurrentProcess().ProcessName != "devenv" )
{
    //...
}
Ограничения такого решения видны сразу - оно будет работать только из-под одной среды разработки - Microsoft Visual Studio. Однако, ничего не мешает расширить проверку, если ваша команда использует другие средства. По крайней мере, тут все прозрачно и предсказуемо.
Так как же сделать проверку правильно? Ок. Есть еще один вариант, который мне кажется самым правильным - завести где-то в общей библиотеке статическую переменную DesignMode, которая по умолчанию будет равна true. А, при запуске программы, сбрасывать ее на false. Это решение стабильно, прозрачно и надежно. Но! Это ужасное решение. Дело в том, что библиотека, содержащая такое решение, накладывает ограничения на свое использование -  подключая такую библиотеку к проекту, нам придется вспомнить о нужных строчках и вписать их, а при исключении библиотеки из проекта, строчку придется удалять. Конечно, ничего криминального в этом нет. Однако, стоит нам предположить, что подход со статистическими флажками приемлем, как мы начнем использовать его повсеместно и наши библиотеки приобретут совершенно непредсказуемое поведение - ведь очень скоро уже никто и не вспомнит какие флажки надо прописать, где, когда и сколько их вообще и какие для чего.

Выводы

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

воскресенье, 16 июня 2013 г.

Выборочный Binding в Ninject

Постановка задачи

Предположим, у нас есть некоторый интерфейс
interface IMyInterface
{
}
И несколько его реализаций, разбросанных по разным сборкам
//Где-то в в какой-то сборке
class ImplementA : IMyInterface
{
}

//Где-то в в какой-то другой сборке
class ImplementB : IMyInterface
{
}
Всем сборкам доступен общий Kernel Ninject-а, через который мы будем биндить наши реализации
//В первой сборке
kernel.Bind<IMyInterface>().To<ImplementA>();
//Во второй сборке
kernel.Bind<IMyInterface>().To<ImplementB>();
Запрашиваться реализации тоже могут в разных местах кода. В том числе и разными разработчиками. Для примера, будем запрашивать их так:
var res = kernel.GetAll<IMyInterface>();
foreach( var item in res )
{
   Console.WriteLine( item.GetType().Name );
}
А теперь, собственно, наша задача - как сделать так, что бы мы получили не все реализации, а только определенные, соответствующие определенным критериям?