воскресенье, 29 ноября 2015 г.

Значимые типы и ООП (в дотнете)

Не в первый раз встретился в интернетах с мнением, что значимые типы в дотнете как-то ортогональны ООП, что ООП совсем не про них.
Обидно стало за милые сердцу структурки и руки снова добрались до блога.

воскресенье, 16 февраля 2014 г.

Регистрация windows — сервисов

Наверное, многим программистам приходится иметь дело с windows—сервисами. Часто - с теми, которые пишутся ими самими или коллегами. Такие сервисы имеют интересную особенность - разрабатывая их или обращаясь к ним в своём коде часто удобно запускать сервисы как обычные приложения (как правило, консольные, но иногда даже и с неким графическим интерфейсом). О том, как наилучшим образом обеспечить эту возможность и пойдёт речь.

четверг, 25 июля 2013 г.

Оператор уточнения типа или static cast в C#

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

пятница, 25 января 2013 г.

Полезные мелочи #1

Недавно мне понадобилось проверить результат компиляции (есть ли ошибки-предупреждения и какие) простого и небольшого фрагмента C# кода будующим компилятором - Microsoft® “Roslyn”.

воскресенье, 20 января 2013 г.

Атрибуты: откуда их брать

Очередной пост из серии "Что такое хорошо и что такое плохо?". В этот раз о том, как правильно извлекать .NET-атрибуты из метаданных. К сожалению, многие для этой задачи используют интерфейс ICustomAttributeProvider, точнее его реализации в классе Assembly и наследниках класса MemberInfo: Type, PropertyInfo и других.

четверг, 20 декабря 2012 г.

Типизированное клонирование

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

вторник, 30 октября 2012 г.

Коллекция как тип свойства

Рано или поздно, но почти перед каждым программистом встаёт задача объявить свойство, тип которого будет представлять собой коллекцию каких-либо объектов. И тут важно не оплошать, что бы свойство было бы удобно использовать и при этом оно не позволило бы случайно "выстрелить себе в ногу".

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

List<T> Items { get; set; }

или

T[] Items { get; set; }

вторник, 23 октября 2012 г.

NVI и C#

NVI - Non-Virtual Interface, одна из идиом программирования, предписывающая, что открытый интерфейс класса не должен быть виртуальным. Подробнее о ней вы можете прочитать в wikibooks и далее в References оттуда. Здесь я расскажу, почему в C# данный подход к проектированию типов имеет не меньшее значение, чем в С++ (почему-то не редко сталкивался с мнением, что это чисто С++-нутая заморочка), а так же о некоторых аспектах применения этого подхода, не описанных (явно) в популярной литературе.

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

MSVS 2012 крашится при run as administrator

Случилось у меня сабжевое несчастье. В EventLog проблема оставила такую информацию о себе:

Faulting application name: devenv.exe, version: 11.0.50727.1, time stamp: 0x5011ecaa
Faulting module name: ntdll.dll, version: 6.1.7601.17725, time stamp: 0x4ec49b8f
Exception code: 0xc0000374
Fault offset: 0x000ce6c3
Faulting process id: 0x5a60
Faulting application start time: 0x01cd9635b48b0467
Faulting application path: c:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\devenv.exe
Faulting module path: C:\Windows\SysWOW64\ntdll.dll
Report Id: f2381422-0228-11e2-9ec6-485b39c60a21

Упущу сложную процедуру поиска причины проблемы, скажу лишь, что всё исправилось с обновлением замечательного расширения VS Commands до последней на сегодня версии 11.2.1.0.

воскресенье, 1 июля 2012 г.

Как написать Equatable или Comparable тип (#2)

В прошлый раз была предложена реализация сравнения объектов на равенство, для унификации которого служат методы типа Object и интерфейс IEquatable<>. Теперь рассмотрим самый простой способ реализовать сравнение объектов на "больше-меньше". Ниже снова будет не только множество скучного кода и ещё более скучных пояснений, но и сниппеты для упрощения его, кода, использования.

вторник, 15 ноября 2011 г.

Как написать Equatable или Comparable тип

Не редко возникает необходимость добавить к типу данных, созданных вами, возможности для сравнения экземпляров друг с другом или с другими объектами. В C#, так уж повелось, это не самая простая операция, чреватая и ошибками и избыточным кодом. Связано это с тем, как мне кажется, что в различных источниках приведены самые разные "паттерны" реализации сравнения и люди, начитавшись (кто-то больше, кто-то меньше) иногда смешивают подходы в одном примере с подходами в другом да добавляют ещё и что-то от себя лично :) Таким образом "паттерны" размножаются, служа пищей для размышления и источником вдохновения для следующего поколения программистов, которые читают доставшийся им код, другие публикации и придумывают свои "паттерны".

суббота, 5 ноября 2011 г.

Компараторы для HashSet<>

Как я попробовал показать в предыдущем сообщении, стандартная реализация компаратора для HashSet<> содержит ошибку, которая состоит в том, что при сравнении элементов хешсета может использоваться внутренний компаратор хешсета, а при расчёте хешкода всегда используется стандартный (EqualityComparer<>.Default).

Настало время эту ошибку исправить. Вдаваясь в долгие и скучные объяснения с различными примерами можно показать, что одним методом без параметров для получения компаратора, как HashSet<>::CreateSetComparer(), нельзя вернуть универсальный компаратор на все случаи жизни. Поэтому придётся использовать два метода:

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

Вот эти методы (класс Comparers у меня содержит всякое безобразие для всевозможных компараторов, например, ещё это и это):

public static partial class Comparers
{
 public static EqualityComparer<HashSet<T>> HashSetComparer<T>() {
   return HashSetEqualityComparer<T>.Default;
 }

 public static EqualityComparer<HashSet<T>> HashSetCustomComparer<T>(IEqualityComparer<T> comparer = null) {
   return new HashSetCustomEqualityComparer<T>(comparer);
 }
}

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

Первый компаратор:

public static partial class Comparers
{
 [Serializable]
 private sealed class HashSetEqualityComparer<T> : EqualityComparer<HashSet<T>>
 {
   private const int MagicNumber = 13;

   static HashSetEqualityComparer() {
     Default = new HashSetEqualityComparer<T>();
   }

   public static new HashSetEqualityComparer<T> Default { get; private set; }

   public override bool Equals(HashSet<T> x, HashSet<T> y) {
     if(x == null) {
       return y == null;
     } else if(y == null) {
       return false;
     } else if(!Equals(x.Comparer, y.Comparer)) {
       return false;
     }//if

     return x.SetEquals(y);
   }

   public override int GetHashCode(HashSet<T> obj) {
     if(obj == null) {
       return 0;
     }//if

     return obj.Aggregate(0, (hash, item) =>
       hash ^ (obj.Comparer.GetHashCode(item) & 0x7FFFFFFF));
   }

   public override bool Equals(object obj) {
     return obj is HashSetEqualityComparer<T>;
   }

   public override int GetHashCode() {
     return MagicNumber;
   }
 }
}

Особенности:

  • Данный компаратор умеет сравнивать только хешсеты с одинаковыми компараторами, зато достаточно оптимально.
  • При вычислении хешкода для получения хешкода элемента хешсета используется внутренний компаратор хешсета.
  • Любые два экземпляра этого компаратора всегда равны между собой (а экземпляров может быть несколько, так как конструктор открытый). Вообще, реализация сравнения в компараторах - отдельная тема. Обратите внимание, что для сравнения компараторов здесь используется метод Object::Equals(object, object), а не оператор сравнения (как, например, в родной реализации компаратора).
  • MaficNumber, не равный нулю, используется из-за того, что равный нулю хешкод обычно возвращается для объектов, имеющих значение null.

Второй компаратор:

public static partial class Comparers
{
 [Serializable]
 private sealed class HashSetCustomEqualityComparer<T> : EqualityComparer<HashSet<T>>
 {
   public HashSetCustomEqualityComparer(IEqualityComparer<T> comparer = null) {
     Comparer = comparer ?? EqualityComparer<T>.Default;
   }

   private IEqualityComparer<T> Comparer { get; set; }

   public override bool Equals(HashSet<T> x, HashSet<T> y) {
     if(x == null) {
       return y == null;
     } else if(y == null) {
       return false;
     } else if(Equals(x.Comparer, y.Comparer) && Equals(x.Comparer, Comparer)) {
       return x.SetEquals(y);
     }//if

     var set = new HashSet<T>(x, Comparer);
     return set.SetEquals(y);
   }

   public override int GetHashCode(HashSet<T> obj) {
     if(obj == null) {
       return 0;
     }//if

     return obj.Aggregate(0, (hash, item) =>
       hash ^ (Comparer.GetHashCode(item) & 0x7FFFFFFF));
   }

   public override bool Equals(object obj) {
     var other = obj as HashSetCustomEqualityComparer<T>;
     return other != null && Equals(other.Comparer, Comparer);
   }

   public override int GetHashCode() {
     return Comparer.GetHashCode();
   }
 }
}

Особенности:

  • Если компараторы сравниваемых хешсетов эквивалентны как между собой, так и с внутренним компаратором, будет использован более оптимальный способ сравнения.
  • При вычислении хешкода для получения хешкода элемента хешсета используется собственный компаратор.

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

В заключении надо сказать, что похожие проблемы есть и у компаратора, который стандартная библиотека предоставляет для SortedSet<>. Я, как мог, постарался описать их на форуме BCL (здесь) и узнал, что, оказывается, это всё by design, то есть "так и задумано". Хотя это может быть и ошибочным мнением. Отдельно на коннект сообщать об этой, второй, похожей проблеме нет желания - много писанины с заранее известным результатом. Буду надеяться, что справившись с опубликованной багой они просмотрят аналогичный код и приведут его в соответствие.

среда, 28 сентября 2011 г.

Баг в реализации HashSet<>::CreateSetComparer()

Не правильно работает компаратор, возвращаемый методом CreateSetComparer класса HashSet<>. Пример кода, демонстрирующий ошибку:

using System;
using System.Collections.Generic;
using System.Diagnostics;

internal sealed class MyItem
{
  public MyItem(int number, string text) {
    Number = number;
    Text = text ?? String.Empty;
  }

  public int Number { get; private set; }
  public string Text { get; private set; }
}

internal sealed class MyItemComparer : EqualityComparer<MyItem>
{
  public override bool Equals(MyItem x, MyItem y) {
    if(x == null) {
      return y == null;
    } else if(y == null) {
      return false;
    } else {
      return EqualityComparer<int>.Default.Equals(x.Number, y.Number);
    }//if
  }

  public override int GetHashCode(MyItem obj) {
    if(obj == null) {
      return 0;
    }//if

    return EqualityComparer<int>.Default.GetHashCode(obj.Number);
  }
}

internal static class Program
{
  private static void Main() {
    var itemComparer = new MyItemComparer();

    var set1 = new HashSet<MyItem>(itemComparer) { new MyItem(1, "One"), };
    var set2 = new HashSet<MyItem>(itemComparer) { new MyItem(1, "1"), };

    var test = set1.SetEquals(set2);
    Debug.Assert(test); // OK

    var setComparer = HashSet<MyItem>.CreateSetComparer();
    var equals = setComparer.Equals(set1, set2);
    Debug.Assert(equals); // OK

    var hash1 = setComparer.GetHashCode(set1);
    var hash2 = setComparer.GetHashCode(set2);
    Debug.Assert(hash1 == hash2, "hash1 == hash2"); // Failed
  }
}


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

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

Update: Желающие могут проголосовать за этот баг на коннекте.

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

О реализации сравнений /* CompareTo() */

Почему-то не редко встречаюсь с мнением, что реализация сравнения двух знаковых целых (Int32) делается (или [наиболее] эффективно может быть сделана) с помощью обычной операции вычитания, например:

static int CompareTo(int x, int y) {
  return x - y;
}

Это не правда. Во-первых, как поведёт себя такая функция сравнения с аргументами:

var compare = CompareTo(Int32.MaxValue, Int32.MinValue);

и, во-вторых (если это не убедительно), посмотрите, наконец, реализацию:

public int CompareTo(int value) {
  // Need to use compare because subtraction will wrap
  // to positive for very large neg numbers, etc.
  if (m_value < value) return -1;
  if (m_value > value) return 1;
  return 0;
}

четверг, 8 апреля 2010 г.

WPF, DataBinding и ToolTip

WPF - интересная технология! Интересная, прежде всего, своими странностями и непонятностями, которые, вкупе с мощью и развесистостью библиотек, не перестают удивлять. :о)) В этот раз "порадовал" датабаиндинг внутри комплексного тултипа (всплывающей подсказки). Примитивнейший, казалось бы, пример:

<Window
  x:Class="WpfApplication2.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="clr-namespace:WpfApplication2"
  Width="300" Height="300"
>
  <Window.Resources>
    <XmlDataProvider x:Key="MyData" XPath="//test:Items/test:Item">
      <XmlDataProvider.XmlNamespaceManager>
        <XmlNamespaceMappingCollection>
          <XmlNamespaceMapping Uri="urn:test" Prefix="test" />
        </XmlNamespaceMappingCollection>
      </XmlDataProvider.XmlNamespaceManager>
      <x:XData>
        <Items xmlns="urn:test">
          <Item Text="Text 1" Description="Description 1" />
          <Item Text="Text 2" Description="Description 2" />
          <Item Text="Text 3" Description="Description 3" />
        </Items>
      </x:XData>
    </XmlDataProvider>
 
    <DataTemplate x:Key="MyTemplate">
      <TextBlock Text="{Binding XPath='@Text'}">
        <TextBlock.ToolTip>
          <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding XPath='@Text'}" />
            <TextBlock Text=" : " />
            <TextBlock Text="{Binding XPath='@Description'}" />
          </StackPanel>
        </TextBlock.ToolTip>
      </TextBlock>
    </DataTemplate>
  </Window.Resources>
 
  <ItemsControl ItemTemplate="{StaticResource MyTemplate}" ItemsSource="{Binding Source={StaticResource MyData}}" />
</Window>

Но если включить вывод отладочной информации из источника System.Windows.Data (см. Trace sources in WPF или недавний пост про Отключение PresentationTraceSources в WPF) то в окошке Output студии можно видеть:

System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:XPath=@Description; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:XPath=@Text; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:XPath=@Description; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 40 : BindingExpression path error: 'InnerText' property not found for 'current item of collection' because data item is null. This could happen because the data provider has not produced any data yet. BindingExpression:Path=/InnerText; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 19 : BindingExpression cannot retrieve value due to missing information. BindingExpression:Path=/InnerText; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 20 : BindingExpression cannot retrieve value from null data item. This could happen when binding is detached or when binding to a Nullable type that has no value. BindingExpression:Path=/InnerText; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=/InnerText; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 40 : BindingExpression path error: 'InnerText' property not found for 'current item of collection' because data item is null. This could happen because the data provider has not produced any data yet. BindingExpression:Path=/InnerText; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')

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

<DataTemplate x:Key="MyTemplate">
  <TextBlock Text="{Binding XPath='@Text'}">
    <TextBlock.ToolTip>
      <ToolTip>
        <StackPanel Name="ToolTip" Orientation="Horizontal">
          <TextBlock>
            <TextBlock.Style>
              <Style TargetType="{x:Type TextBlock}">
                <Setter Property="Text" Value="{Binding XPath='@Text'}" />
                <Style.Triggers>
                  <Trigger Property="DataContext" Value="{x:Null}">
                    <Setter Property="Text" Value="" />
                  </Trigger>
                </Style.Triggers>
              </Style>
            </TextBlock.Style>
          </TextBlock>
          <TextBlock Text=" : " />
          <TextBlock>
            <TextBlock.Style>
              <Style TargetType="{x:Type TextBlock}">
                <Setter Property="Text" Value="{Binding XPath='@Description'}" />
                <Style.Triggers>
                  <Trigger Property="DataContext" Value="{x:Null}">
                    <Setter Property="Text" Value="" />
                  </Trigger>
                </Style.Triggers>
              </Style>
            </TextBlock.Style>
          </TextBlock>
        </StackPanel>
      </ToolTip>
    </TextBlock.ToolTip>
  </TextBlock>
</DataTemplate>

То есть баиндинг выставляется через стиль, а затем в DataTrigger-е сбрасывается в случае, когда данных нет.

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

пятница, 2 апреля 2010 г.

Сниппеты для проверки аргументов на null

Думаю, есть время поделиться моими любимыми и наиболее часто используемыми сниппетами.

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

Не секрет, что для упрощения проверок аргументов на null изобретено не мало средств: от использования возможностей языка\компилятора до специальных инструментов a-la CodeContracts, аннотоций ReSharper-а или колдунства PostSharp-а.

Мне всё это (пока что) кажется излишним оверхедом: тратить время на разбор выражений обидно, с контрактами действительно пока не всё понятно, завязываться же на инструмент третий стороны не хочется вовсе.

Итак, как делаю я: написав объявление метода и операторные скобки к нему, обозначив тело:

void MyMethod(object param1, object param2) {
  | // <- это позиция курсора в редакторе
}

…набираю "an" (это shortcut для сниппета), жму Tab и получаю:


if(ArgName == null) {
  throw new ArgumentNullException("ArgName");
}//if

Осталось ввести имя аргумента (курсор уже там, где нужно, в выделенном квадрате + помогает IntelliSense) и нажать Enter:

void MyMethod(object param1, object param2) {
  if(param1 == null) {
    throw new ArgumentNullException("param1");
  }//if
  |
}

Можно приступать к написанию тела метода.

Но если нужно проверить и второй параметр, то ставлю курсор перед закрывающей скобкой: }//if:

void MyMethod(object param1, object param2) {
  if(param1 == null) {
    throw new ArgumentNullException("param1");
 |}//if
}

…набираю "an2", жму Tab и получаю:

void MyMethod(object param1, object param2) {
  if(param1 == null) {
    throw new ArgumentNullException("param1");
  } else if(ArgName == null) {
    throw new ArgumentNullException("ArgName");
  }//if
}

Опять быстро с помощью IntelliSense ввожу имя второго параметра, жму Enter:

void MyMethod(object param1, object param2) {
  if(param1 == null) {
    throw new ArgumentNullException("param1");
  } else if(param2 == null) {
    throw new ArgumentNullException("param2");
  }//if
  |
}

Вот так вот несколькими нажатиями я "набираю" довольно много нужного кода.

В добавок к сниппетам "an" и "an2" у меня есть сниппеты ("ans" и "ans2" соответственно) для проверки строк, которые отличаются от первых тем, что вместо проверки на null в условии выполняется проверка аргумента с помощью String.IsNullOrEmpty(string).

Выглядит первый ("an") сниппет так:

<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>Throws ArgumentNullException</Title>
      <Shortcut>an</Shortcut>
      <Description>Code snippet for throws ArgumentNullException</Description>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
      <Author>Viacheslav.Ivanov@GMail.com</Author>
    </Header>
    <Snippet>
      <Declarations>
        <Literal>
          <ID>ArgumentName</ID>
          <ToolTip>Name of the argument</ToolTip>
          <Default>ArgName</Default>
        </Literal>
        <Literal Editable="false">
          <ID>ArgumentNullException</ID>
          <Function>SimpleTypeName(global::System.ArgumentNullException)</Function>
          <ToolTip>Type of the exception</ToolTip>
        </Literal>
      </Declarations>
      <Code Language="CSharp">
<![CDATA[if($ArgumentName$ == null) {
      throw new $ArgumentNullException$("$ArgumentName$");
    }//if
    $end$]]>
      </Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

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

Проверка второго и последующих аргументов на null ("an2"):

<Code Language="CSharp">
<![CDATA[} else if($ArgumentName$ == null) {
      throw new $ArgumentNullException$("$ArgumentName$");$end$]]>
</Code>

Проверка строки на IsNullOrEmpty ("ans"):

<Snippet>
  <Declarations>
    <Literal>
      <ID>ArgumentName</ID>
      <ToolTip>Name of the argument</ToolTip>
      <Default>ArgName</Default>
    </Literal>
    <Literal Editable="false">
      <ID>String</ID>
      <Function>SimpleTypeName(global::System.String)</Function>
      <ToolTip>System.String type</ToolTip>
    </Literal>
    <Literal Editable="false">
      <ID>ArgumentNullException</ID>
      <Function>SimpleTypeName(global::System.ArgumentNullException)</Function>
      <ToolTip>Type of the exception</ToolTip>
    </Literal>
  </Declarations>
  <Code Language="CSharp">
<![CDATA[if($String$.IsNullOrEmpty($ArgumentName$)) {
      throw new $ArgumentNullException$("$ArgumentName$");
    }//if
    $end$]]>
  </Code>
</Snippet>

Проверка второго и последующий аргументов строкового типа на IsNullOrEmpty ("ans2"):

<Code Language="CSharp">
<![CDATA[} else if($String$.IsNullOrEmpty($ArgumentName$)) {
      throw new $ArgumentNullException$("$ArgumentName$");$end$]]>
</Code>

Скачать готовые сниппеты можно отсюда. О том, как их подключить к MSVS можно прочитать в статье How to: Manage Code Snippets.

четверг, 11 марта 2010 г.

Возрождение :о))

Ура!

Мне наконец-то удалось найти удобный способ написания сообщений в свой же блог, а это была единственная причина, останавливавшая меня от приступов графоманства :о) Мне удалось достаточно быстро немного переформатировтаь предыдущие посты (прошу прощение, если кому-то в rss помешали мои всплывшие сообщения) и шустро написать один новый.

Надеюсь, полосы прокрутки для кода никому сильно не помешают, мне показалось что они удобнее, чем перенос строк.

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

Кому интересно, то технология у меня простая - создаю в MSVS (2010) новую HTML страничку и в ней набираю то, что хочу опубликовать. Сложно было разобраться с переносами - наконец-то нашёл, где тут переключатели, позволяющие не реагировать текстовому процессору на обычный перевод строки и обращать внимание только на соответствующие явные средства HTML a-la <br /> и <p />. Есть один общий плюс к каждому посту свой собственный такой переключатель.

Весь код и его раскраску набираю сам врукопашную же в том же текстовом редакторе студии. Оказывается, это совсем не сложно :о)) Xml набирать сложнее :о)) Ни одного достаточно удобного плагина к студии на эту тему отыскать не сумел.

Зато выглядит теперь всё так, как мне нравится, а, значит, развивать сие будет интереснее.

Отключение PresentationTraceSources в WPF

Если вы когда-либо отлаживали WPF-приложение, то могли видеть в окошке Output отладчика примерно такой вывод:

System.Windows.Data Error: 4 : Cannot find source for binding with reference …
System.Windows.Data Error: 39 : BindingExpression path error: …

и тому подобное. Это "работает" класс PresentationTraceSources. Подробнее о нём можно узнать в статьях Trace sources in WPF и How can I debug WPF bindings?.

Я расскажу не о том, зачем нужен этот класс и не о том, как им пользоваться, а о том, как же его "отключить", то есть как добиться того, что бы в Output не писалось то, что вам, может быть, и не нужно.

Самый простой способ отключения вывода PresentationTraceSources - програмный:

Action<TraceSource> disable = traceSource => traceSource.Switch.Level = SourceLevels.Off;
disable(PresentationTraceSources.AnimationSource);
disable(PresentationTraceSources.DataBindingSource);
disable(PresentationTraceSources.DependencyPropertySource);
// … И так далее по всем имеющимся TraceSource-ам

Но этот способ и самый неинтересный, потому что его трудно поддерживать: необходимо изменить код (и, как следствие - перекомпилировать программу) что бы добавить, убрать или как-то более хитро настроить полезный зачастую вывод.

Правильный путь: воспользоваться файлом конфигурации. Но и следующее решение не будет работать:

<configuration>
  <system.diagnostics>
    <sources>
      <source name="System.Windows.Data" switchValue="Off" />
      <!-- и так далее … -->
    </sources>
  </system.diagnostics>
</configuration>

при запуске программы из-под отладчика, из-за того, что внутри PresentationTraceSources при создании экземпляра TraceSource проверяется, не подключён ли отладчик, и если подключён, и для switchValue указано значение Off, то будет использоваться значение SourceLevels.Warning.

Зная вышесказанное, не сложно исхотриться так:

<source name="System.Windows.Data" switchValue="Critical" />
<!-- и так далее … -->

где Critical - самый строгий уровень вывода, но отличный от Off. На практике ни одного сообщения с критическим уровнем важности я ещё не встечал, поэтому в ситуациях, когда хочется по возможности максимально избавиться от ненужного вывода, можно воспользоваться описанным советом.

На последок, полный пример с небольшой универсализацией, позволяющей задавать уровень вывода PresentationTraceSources один раз:

<configuration>
  <system.diagnostics>
    <sources>
      <source name="System.Windows.Data" switchName="PresentationTraceSwitch" />
      <source name="System.Windows.DependencyProperty" switchName="PresentationTraceSwitch" />
      <source name="System.Windows.Documents" switchName="PresentationTraceSwitch" />
      <source name="System.Windows.Freezable" switchName="PresentationTraceSwitch" />
      <source name="System.Windows.Interop.HwndHost" switchName="PresentationTraceSwitch" />
      <source name="System.Windows.Markup" switchName="PresentationTraceSwitch" />
      <source name="System.Windows.Media.Animation" switchName="PresentationTraceSwitch" />
      <source name="System.Windows.NameScope" switchName="PresentationTraceSwitch" />
      <source name="System.Windows.ResourceDictionary" switchName="PresentationTraceSwitch" />
      <source name="System.Windows.RoutedEvent" switchName="PresentationTraceSwitch" />
      <!-- Для 3.5 указано всё, что есть -->
    </sources>
    <switches>
      <!-- Do not use an "Off", because under debugger it's replaced to "Warning". -->
      <add name="PresentationTraceSwitch" value="Critical" />
    </switches>
  </system.diagnostics>
</configuration>

И не забудьте где-либо в коде вашей программы вызвать

PresentationTraceSources.Refresh();

без вызова метода Refresh() значения не будут зачитаны из конфигурационного файла.

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

“Парные” методы

Значение термина, вынесенного в заголовок, скорее всего никому кроме меня неизвестно. Это потому, что я сам его придумал, так как не знаю точного названия того, о чём хочу рассказать. А речь пойдёт о методах, которые обязательно должны быть вызваны вместе. Примерами таких служат ListBox.BiginUpdate() и ListBox.EndUpdate() или CodeAccessPermission.Assert() и CodeAccessPermission.RevertAssert().

Объединяет эти методы то, что вызвав первый из них (дальше я буду называть его Begin-вызовом) программист обязан (в случае, если вызов завершился успешно) вызвать и второй (я буду называть его End-вызов). Часто реализуют такие вызовы следующим образом:

listBox.BeginUpdate(); // Begin-вызов
try {
  // Некоторая полезная работа
  listbox.Items.Add("a");
  listbox.Items.Add("b");
  listbox.Items.Add("c");
} finally {
  listBox.EndUpdate(); // End-вызов
}//try

то есть код после Begin-вызова заключают в блок try, а End-вызов в finally. Это позволяет (в скобках заметим, худо-бедно) гарантировать, что в случае возникновения исключения между вызовами объект, методы которого вызываются, останется в согласованном состоянии.

Конечно, использовать try-finally каждый раз при одинаковых вызовах очень неудобно. О способе избежать такого неудобства я и расскажу.

Обычно, для упрощения вызова “парных” методов, используют класс-хелпер, реализующий IDisposable Interface, и вызывающий в своей реализации Dispose() End-метод:

internal sealed class ListBoxUpdateHelper : IDisposable
{
  private readonly ListBox listBox;
 
  public ListBoxUpdateHelper(ListBox listBox) {
    if(listBox == null) {
      throw new ArgumentNullException("listBox");
    }//if
 
    this.listBox = listBox;
    ListBox.BeginUpdate();
  }
 
  private ListBox ListBox {
    [DebuggerStepThrough]
    get { return listBox; }
  }
 
  public void Dispose() {
    ListBox.EndUpdate();
  }
}

Теперь вызывать BeginUpdate и EndUpdate удобнее:

using(new ListBoxUpdateHelper(listBox)) {
  // Некоторая полезная работа
  listbox.Items.Add("a");
  listbox.Items.Add("b");
  listbox.Items.Add("c");
}//using

Мне он не нравится по той причине, что требует необходимости завести новую сущность – тип-хелпер и всюду в месте применения её использовать. Для пользователя библиотеки это может оказаться сложным: что бы легко и удобно пользоваться некоей функциональностью типа (ListBox в нашем примере) надо “позвать на помощь” другой тип. А если различных парных методов требуется несколько? Несколько и типов-хелперов.

Поэтому прогрессивное сообщество пошло дальше, вместо типа-хелпера предоставив пользователю метод-хелпер:

internal static class ListBoxExtensions
{
  public static IDisposable DoUpdate(this ListBox listBox) {
    return new ListBoxUpdateHelper(listBox);
  }
 
  private sealed class ListBoxUpdateHelper : IDisposable
  {
    private readonly ListBox listBox;
 
    public ListBoxUpdateHelper(ListBox listBox) {
      if(listBox == null) {
        throw new ArgumentNullException("listBox");
      }//if
 
      this.listBox = listBox;
      ListBox.BeginUpdate();
    }
 
    private ListBox ListBox {
      [DebuggerStepThrough]
      get { return listBox; }
    }
 
    public void Dispose() {
      ListBox.EndUpdate();
    }
  }
}

Который можно использовать так:

using(listBox.DoUpdate()) {
  // Некоторая полезная работа
  listbox.Items.Add("a");
  listbox.Items.Add("b");
  listbox.Items.Add("c");
}//using

Теперь “снаружи” виден лишь один метод DoUpdate(), по типу возвращаемого значения которого (IDisposable) вызывающий будет знать, что метод, по возможности, лучше вызвать в блоке using.

Указанный подход можно найти в большом количестве библиотек, например в R Smart Application Toolkit.

Но меня не устраивает и этот вариант. Вернее, громозкозть его реализации. Спустя какое-то время увлечения данной методикой мне надоело писать большое количество классов-хелперов (которые по прежнему остались в виде private) и я начал пользоваться таким вот классом:

public static class Disposable
{
  public static IDisposable New(Action dispose) {
    return new ActionDisposable(dispose);
  }
 
  [Serializable]
  [DebuggerDisplay("{ Action != null ? \"<Action>\" : \"<Action (Empty)>\" }")]
  private sealed class ActionDisposable : IDisposable
  {
    public ActionDisposable(Action action) {
      Action = action;
    }
 
    private Action Action { get; set; }
 
    public void Dispose() {
      if(Action != null) {
        Action();
        Action = null;
      }//if
    }
  }
}

С ним упростилось написание методов, аналогичных вышеприведённому DoUpdate():

internal static class ListBoxExtensions
{
  public static IDisposable DoUpdate(this ListBox listBox) {
    listBox.BeginUpdate();
    return Disposable.New(listBox.EndUpdate);
  }
}

Что позволяет быстрее и проще, а, значит, и чаще использовать описанный паттерн (не побоюсь этого слова :о) – а кстати, как его можно назвать?).

Подробнее о классе Disposable и ещё об одном обнаруженном с его помощью “паттерне” я раскажу как-нибудь в следующий раз.

P.S. Касательно “худо-бедно”: существует возможность того, что поток, в котором выполняется рассматриваемый нами код, будет прерван вызовом Thread.Abort(…) из другого потока сразу после вызова Begin-метода и перед тем, как начнётся try-блок. Источник: Locks and exceptions do not mix.