Дмитpий Hecтepук

Блог о программировании — C#, F#, C++, архитектура, и многое другое

Archive for the ‘C#’ Category

Что нового в C# 7

2 комментария

Ну что, всех можно поздравить с релизом VS2017? Для начала, я пожалуй признаюсь что я давно уже (время наверное на месяцы пошло) использую Rider (и даже запилил по нему видеокурс), но релиз VS2017 — это не только релиз слегка медленной, 32-битной Windows-only IDE которая даже не умеет правильно рисовать лигатуры. Это еще и релиз новых версий компиляторов — именно того, что Microsoft нам обещали делать out-of-band, но обещание оказалось наглой ложью, как для C++ так и для C#.

Что же, давайте посмотрим что там в новом сишарпике…

Локальные методы

Вообще, проблемы с локальными методами толком не было, т.к. в любой функции можно декларировать лямбда-переменную, что и есть по сути метод. Так что в C#7 это разве что привели в более удобоваримый вид:

public static Tuple<double,double> Solve(double a, double b, double c)
{
  double CalculateDiscriminant()
  {
    return b * b - 4 * a * c;
  }
  var disc = CalculateDiscriminant();
  return Tuple.Create(
    (-b + Math.Sqrt(disc)) / (2 * a),
    (-b - Math.Sqrt(disc)) / (2 * a)
  );
}

Что мы можем заметить в примере выше? Правильно, вложенная функция умеет захватывать окружение, в т.ч. аргументы внешней функции. И конечно же дать функции CalculateDiscriminant аргументы a,b,c не получилось бы, т.к. эти имена уже заняты.

Ложка дегтя — расположение метода в другом методе может быть где угодно, то есть можно сначала использовать а потом задекларировать. Это на любителя, конечно, но тут главное — прямые руки и здравый смысл.

Касательно полезности фичи: мне норм. Этот подход позволяет плодить меньше членов на уровне класса, а баловаться с лямбдами напряжно т.к. для любой лямбды нужно определить тип Func<int,Foo,string> и это порой бывает утомительно — хотя в некоторых случаях Решарпер помогает. Вообще было бы лучше если бы нам позволяли еще делать локальные статические переменные прямо в методах, как это позволяет делать C++.

Out-переменные

Обычно как было: декларация переменной это statement. Все, теперь это по сути expression т.к.:

string s;
if (DateTime.TryParse(s, out DateTime dt))
{
  // используем dt
}

Это чисто «сахарная» фича, и резонным будет вопрос — а что если DateTime не пропарсится а мы все равно попытаемся ей воспользоваться? Ну, тут все просто — у этой штуки будет дефолтное значение. Но зато заметьте, dt имеет тот же scope, что и строка s.

Кортежи

Кортежи уже были в C#, но вставлены они были криво: все эти Item1, Item2 не добавляли к удобству пользования, и кому-то могло показаться что рано или поздно нам вообще подсунут variadic templates. Но разрабы поймали себя и все-таки сделали что-то годное:

public static (double x1, double x2) Solve(double a, double b, double c)
{
  var disc = CalculateDiscriminant();
  double CalculateDiscriminant()
  {
    return b * b - 4 * a * c;
  }
  return (
    (-b + Math.Sqrt(disc)) / (2 * a), 
    (-b - Math.Sqrt(disc)) / (2 * a)
  );
}

И вызвать можно это вот так:

var (x1, x2) = QuadraticEquationSolver.Solve(1, 10, 16);
Console.WriteLine(x1 + ", " + x2);

Ну а если декларировать кортежи на месте, то правила те же самые, и есть варианты — давать элементам кортежа имена или нет:

var names = (first: "Dmitri", last: "Nesteruk");
Console.WriteLine(names.first);

Как видите, кортеж определяется как тип через скобочки и список типов (т.е. (double,double) условно-эквивалентно ValueTuple<double,double>). Но ложка дегтя тут огромная: для всего этого нужен внешний пакет System.ValueTuple. Что?!?!?

Спокойно, очевидно что просто эту тему недопилили и ValueTuple<T, ...>, который должен был быть в .NET 4.6.2 BCL просто туда не попал. Что это за класс? Да просто еще одна реализация кортежа, но в этот раз вся ее суть закрыта синтаксическим сахаром вроде того что я привел выше. Если кому интересно что под капотом, вот сорцы.

Expression-bodied members

На момент C#6, некоторые вещи можно было писать как expression bodies, т.е например void Foo(x) => x+1; делал вполне себе валидный метод. Сейчас же, это счастье распространилось помимо методов и пропертей еще на конструкторы/деструкторы, геттеры и сеттеры:

class Person
{
  Person() => Names = new[]{"Nameless One"};
  public int FirstName
  {
    get => Names[0]; // ну вы поняли
  }
}

Вообще вся эта expression bodied тематика экономит хорошо так символов. Единственное что мне не нравится это каша вроде

class Person
{
  bool CanVote => Age <= 16; // без лигатур - коряво :(
}

но это просто один конкретный пример того как можно получить нечитаемый код.

Throw Expressions

Тут мне 2017 студия осмелилась подправить Решарперный код. Было что-то вроде этого:

class Foo
{
  Foo(Bar bar)
  {
    if (bar == null) throw new SomeException();
    this.bar = bar;
  }
}

но теперь оказывается можно писать вот так

class Foo
{
  Foo(Bar bar)
  {
    this.bar = bar ?? throw new SomeException();
  }
}

Интересный ход конем, однако. Хорошо ли бросать из места где ожидается конкретный return value? Ну, если компилятор может это безопасно обработать, почему бы и нет — главное потом не пытаться прочитать поле, которое мы так и недописали.

Pattern matching

Когда-то я писал про самопальный pattern matching, но тут сделали реальную реализацию.

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

if (shape is Rectangle rc)
{
  var area = rc.Width * rc.Height;
}

Ну и потом нам разрешили делать switch на этом месте:

Shape s = new Rectangle { width = 0, height = 0 };
switch (s)
{
  case Rectangle rc when (rc.width == rc.height):
    Console.WriteLine("It's a square!");
    break;
  case Circle c:
    break;
}

Заметьте ключевое слово when выше. Ничего не напоминает? Правильно, F#. Но конечно F# все еще лидирует в этом плане. Хотя бы потому, что в F# можно делать алгебраические типы данных, что очень полезно когда ты, например, что-нибудь структурированное парсишь, например MathML.

Ну хорошо, смотрите, хоть что-то сделали. Конечно, нельзя мэтчить на Rectangle(42, double h) — это откинули на будущее. И switch на кортежах тоже пока не работает. Грусть-печаль.

Ref returns

Как вернуть элемент массива by reference? Ну строго говоря, можно запинить весь массив и попытаться вернуть указатель в unsafe. Но теперь есть более гуманный способ — ref на чем угодно.

int[] numbers = new int[] { 1, 2, 3 };
ref int refToSecond = ref numbers[1];
refToSecond = 123;
Console.WriteLine(string.Join(",",numbers)); // 1, 123, 3

Хмм, ну это хорошо конечно, но со списком такое не пройдет

List<int> numbers = new List<int> { 1, 2, 3 };
ref int second = ref numbers[1]; // A property or indexer cannot be used⋮

Хмм, да, полезность сей затеи стремится к нулю. Нельзя например сослаться на букву в строке.

Все это — простой aliasing, то есть еще одно имя для той же самой переменной. Это не «ссылка на область памяти» т.к. это обычно требует слово fixed чтобы вменяемо работать в unsafe контексте, а у нас все тут safe.

Да, теперь можно и из метода ref на переданный ref возвращать, то есть возможно вот такое мракобесие:

static ref int Min(ref int x, ref int y) {
  if (x < y)
    return ref x;
  else
    return ref y;
}
⋮
int a = 1, b = 2;
 ref int c = ref Min(ref a, ref b);
c = 123; // a == 123!!!

Holy s~t, посмотрите сколько тут раз ref написано. Даже в вызове используется ref Min(...) потому что если ты опустишь этот ref это значит что тебе не нужен референс на значение, а нужно само значение.

int d = Min(ref a, ref b); // вполне валидно, d == 1

Проблема тут в том, что этот ref — это то же самое что референсы в C++, из-за которых теперь в уютном сишарпике можно писать треш вроде Min(ref a, ref b) += 123 (по сути делает a += 123), что абсолютно отвратительно для читания и понимания. К тому же, неопытные программисты тут же будут ломиться и возвращать адреса локальных переменных, что делать нельзя:

static ref int Foo()
{
  int x = 0;
  return ref x; // не скомпилируется - cannot return local by reference
}

Хотя помнится что году так в 2012, Липперт угрожал что они могут и этот сценарий поддержать.

Что еще тут?

Теперь можно делать подчеркивания в литералах: int x = 123_456;. Также появились бинарные литералы — теперь можно писать var literal = 1100_1011_____0101_1010_1001;. Подчеркиваний может быть сколько угодно.

Pattern matching — все самое вкусное не попало в C#7, ждем следующих версий. Должны быть всякие вот такие штуки поддержаны:

switch (foo)
{
  case 123: ⋮
  case MyEnum.Stuff: ⋮
  case string s: ⋮
  case Point(int x, 321) where x > 0: ⋮
  case Point(12, 23) : ⋮
  default: ⋮
}

Record types — не вошли, будут в C#8 наверное.

Ну вообщем как-то так. А вы заметили насколько шустрее стал компилятор C#7? Серьезно, видимо опять что-то подкрутили с инкрементальностью. Кстати, в VS2015 сделали обалденные улучшения инкрементальности C++, а тут кажется улучшили C#. У меня «горячая» компиляция летает, хотя «холодная» все еще тормозит как и раньше.

Наверняка что-то еще из фич забыл, напишите в комментариях. ■

Written by Dmitri

13 марта 2017 at 10:54

Опубликовано в C#

Мысли о «новом» обещанном нам C#

9 комментариев

Как вы понимаете, Microsoft сейчас толком не до языковых фич т.к. сам компилятор переделали и даже уже сами переключились на Roslyn в процессе разработки, что как бы намекает на большие планы на 2014. Соответственно, те фичи которые Мадс осветил на NDC London не являются системообразующими, поэтому получите список:

  • Первичный конструктор в стиле class Size(int cx, int cy) { ... }, где как вы догадались, cx и cy это значения которые потом можно присвоить пропертям. Позволяет писать new Size(320,240), и вообще своровано с F#.
  • Возможность задавать значения readonly свойствам в стиле public int Width { get; } = cx. Даже не знаю что тут сказать – у меня часто свойства еще и в UI используются, а там все равно будет поле, так что нет разницы. (Ждите от Roslyn автореализации INPC? Не дождетесь.)
  • Property expressions, т.е. возможность писать property get без собственно блока get{}, return и прочих излишеств:
    public int Area => Width * Height;
    
  • Та же тема для методов. Фактически, просто удаление скобочек и return, но я уже вижу как новички пихают в эти стейтменты всякий мусор:
    public Point Move(int dx, int dy) => new Point(X + dx, Y + dy);
    
  • params IEnumerable, потому что когда хочется неограниченное число аргументов, то IEnumerable удобнее чем массив? На самом деле, могли как в D попробовать делать вещи через convention over configuration, т.е. вместо ключевого слова params просто сказать, что если есть только один параметр и он IEnumerable<T> или T[], то давать пользователям передавать массив через Foo(a,b,c) – и все были бы довольны.
  • Monadic null checking означает что вместо person.With(x => x.Address).With(x => x.HouseName) можно написать person?.Address?.HouseName, и везде будут проверки на null. Шикарно конечно, может немного поздновато, к тому же как показала практика, проверка на null – это не единственный concern, которых можно вкладывать в цепочки из лямбд.
  • Вывод типов для конструкторов, т.е. вместо Tuple.Create(foo,bar) можно наконец-то писать new Tuple(foo,bar)
  • Инлайновые декларации для out параметров по мне так самый большой фейл, пожалуй. Идея в том чтобы можно было писать
    if (foo.TryGetValue(out int x))
    {
      // use x, it's already been declared!
    }
    

    Ну и конечно идея в том что если параметров возврата несколько, то это как бы упрощает жизнь. Знаете что еще упрощает жизнь?

    let (x1,x2) = SolveQuadratic(1,10,16);
    

    И не важно, получили ли мы кортеж из (x1,x2) или просто две переменных – главное что результат метода записывается в том месте, в котором вы ожидаете, а не в аргументе, где декларации переменной – явно не место.

  • Импорт типа в пространство имен: уже хорошо, сделаем using System.Math чтобы не пользоваться поистине невменяемыми с точки зрения здравого смысла конструкциями вроде Math.Sin(). Хотя это только полумера: еще нужно удалить заглавные буквы (ну зачем они?), добавить оператор возведения в степень (с целочисленной перегрузкой), и уже будет можно дышать. Но разве ж кто-то об этом думает?

Ничего из списка выше особо не впечатляет. Гораздо интересней сам Roslyn (все же надеюсь что он будет «готов» к VS2014), ну и как это не странно, С++ сейчас развивается как-то быстрее, причем без всякого переписывания компилятора. ■

Written by Dmitri

21 декабря 2013 at 13:36

Опубликовано в C#

Управление подписками на события

8 комментариев

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

Для начала, я создаю класс DisposableCollection который умеет вызывать Dispose() на каждом элементе коллекции:

public sealed class DisposableCollection : Collection<IDisposable>, IDisposable
{
  public void Dispose()
  {
    foreach (var obj in this)
      obj.SafeDispose();
  }
}

SafeDispose() в коде выше – это всего лишь Dispose() с проверкой на null. Метод также возвращает «флаг успешности», что порой бывает полезно.

public static bool SafeDispose(this IDisposable thіs)
{
  if (thіs != null)
  {
    thіs.Dispose();
    return true;
  }
  return false;
}

Еще один extension method — это инверсия потока управления для добавления элементов в коллекцию:

public static T AddTo<T>(this T thіs, Collection<T> coll)
{
  coll.Add(thіs);
  return thіs;
}

А теперь всем этим можно пользоваться. Для подписок используем ReactiveExtensions.

public sealed class XmppConnectionManager : IResetable
{
  private XmppClient client;
  private DisposableCollection subscriptions;
  private void InitializeEvents()
  {
    // wire up events
    Observable.FromEventPattern<ExceptionEventArgs>(x => client.OnError += x, x => client.OnError -= x)
      .Subscribe(x => OnError(x.EventArgs))
      .AddTo(subscriptions);
    Observable.FromEventPattern<EventArgs>(x => client.OnLogin += x, x => client.OnLogin -= x)
      .Subscribe(x => OnLogin(x.EventArgs))
      .AddTo(subscriptions);
    ...
  }
}

Соответственно, если нужно вдруг отписаться от всех подписок, это делается очень быстро:

subscriptions.SafeDispose();

А теперь загадка: почему методы расширения, приведенные выше, компилируются несмотря на название параметра thіs? Дам намек: как ключевое слово имя параметра не подсветилось.

Written by Dmitri

2 августа 2011 at 6:27

Опубликовано в .NET, C#, rx

Хранение preset-ов в setting-ах

4 комментария

Сегодня я зарелизил версию 1.7.2 Типографикса, добавив всего одну фичу – возможность держать несколько наборов настроек. Сделал я это для того, чтобы можно было сохранять настройки редактора для разных платформ – в моем случае, для этого блога и devtalk, а также для Хабра, ГДН, CodeProject’а и других систем.

Примечательно то, что простенькая задача сохранить в словарике несколько пресетов и потом записать все это в Properties.Settings превратилась в непростую затею.

Первая попытка

Сначала я сделал все правильно: создал Dictionary<string, ConversionOptions>, то есть мэп названий пресетов на их значения, и тупо попробовал записать все это в настройки. Ничего не получилось. Оказывается, большое количество классов в WPF (такие как Color или Thickness) не сериализуются! Это значит, что по сути дела чтобы сериализовать тип мне нужно их выкинуть!

Следующей проблемой оказалось то, что .Net бросал исключение при попытке сериализовать событие PropertyChanged. Это правда очень быстро релилось прописыванием [field: NonSerialized], но толком делу не помогло.

Вторая попытка

Проблема в том, что несмотря на то, что все работало, настройки отказались записывать Dictionary<>. Никаких исключений не было, тип просто не сохранялся в Properties.Settings и воответственно при попытке считать, возвращался null.

Самое простое в этом случае (точно так же как и в случае с несериализуемыми типами) – это сконвертировать структуру не в XML а в… JSON! (Формат выбран произвольно.) Соответственно, я скачал ServiceStack.Text и заменил тип свойств в Settings на string. В результате, сериализация происходит вот так:

Settings.Default["OptionPresets"] =
  JsonSerializer.SerializeToString(OptionPresets, typeof (Dictionary<string, ConversionOptions>));

Ну а что касается тех «несериализуемых» полей то, увы, приходится делать строковые или аналогичные backing fields, со всемы вытекающими последствиями.

Written by Dmitri

5 июня 2011 at 23:22

Опубликовано в .NET, C#

Уравнения на F# проще чем на C#?

22 комментария

На недавней встрече, посвещенной языку F#, я показал как якобы «элегантно» можно описывать математические функции на F#. А сейчас сел и задумался – так ли это на самом деле? Давайте разберемся.

Квадратное уравнение

Начнем с примера, приведенного мною на встрече – решения квадратного уравнения. Казалось бы – что может быть проще?

Сразу можно сделать несколько сравнений C# и F# для этого примера:

  • Возвращаются 2 значения, а следовательно F# выигрывает за счет того что не надо писать Tuple.Create, а тому кто эти значения получит не надо использовать абсолютно ничего не значащие свойства Item1 и Item2
  • Математические binding’и у F# намного «ближе» чем у C#, а следовательно в этом случае (да и в общем), ваш код на F# не будет испачкан явным вызовом статических функций вроде Math.Sqrt. Меня всегда это бесило в C# т.к. сложные вычисления становится еще сложнее читать.
  • Ни C# ни F# не умеют работать с оператором ±. Но если в C# нам придется тупо вызывать вычисления дважды, в F# мы можем просто определить функцию конечного вычисления, а потом передать в нее операторы (+) и (-).

В результате получим следующее сравнение:

C#

public Tuple<double,double> SolveQuadraticEquation(double a, double b, double c)
{
  double discRoot = Math.Sqrt(b*b-4.0*a*c);
  double x1 = (-b + discRoot) / (2 * a);
  double x2 = (-b - discRoot) / (2 * a);
  return Tuple.Create(x1, x2);
}

F#

let solveQuadratic a b c =
  let disc = b * b - 4.0 * a *c
  let calc op = (op (-b) (sqrt disc)) / (2.0*a)
  (calc (+), calc(-))

Пока что разница небольшая – в одну строчку, если не считать фигурных скобок. А спорим я могу сократить эту разницу практически до нуля? Вот, пожалуйста:

C#

public Tuple<double, double> SolveQuadraticEquation(double a, double b, double c)
{
  double q = -0.5 * (b + Math.Sign(b) * Math.Sqrt(b * b - 4 * a * c));
  return Tuple.Create(q / a, c / q);
}

F#

let solveQuadraticEquation a b c =
  let q = -0.5 * (b + (sign b |> float) * sqrt(b * b - 4.0 * a * c))
  (q / a, c / q) //           ^^^^^^^^ WTF?!?

Пример на F# должен вызвать у вас возмущение – с какой это радости результат (sign b) должен быть приведен к типу float?1 А дело все в непродуманности API, согласно которому sign(x) – это всегда int (могли бы сделать тем же типом что и аргумент), а также крайне жесткой системе типов в которой float + int * float не воспринимается и никак не вычисляется.

Такое странное поведение F# унаследовано из OCaml и требуется для правильного вывода типов. Если вам действительно интересны причины – можно почитать тут. Мне же это лишь палки в колеса. Да, и я знаю что можно было бы написать sign(float b) или что-то в этом роде. Тут как бы без разницы, все равно «не очень».

Комплексные решения

Ну да ладно, а вот вопрос – что будет если дискриминанта (Δ) меньше нуля? С одной стороны, и C# и F# поддерживает комплексные типы данных (см. System.Numerics). Если сделать вид что мы все будем передавать как комплексные числа (так проще?), то получим вот такой вот код:

C#

public Tuple<Complex, Complex> SolveQuadraticEquation3(double a, double b, double c)
{
  double det = b * b - 4 * a * c;
  double absRoot = Math.Sqrt(Math.Abs(det));
  Complex root = det < 0 ? new Complex(0, absRoot) : new Complex(absRoot, 0);
  Complex q = -0.5 * (b + Math.Sign(b) * root);
  return Tuple.Create(q / a, c / q);
}

F#

let solveQuadratic3 a b c =
  let complex v = Complex(v, 0.0)
  let det = b * b - 4.0 * a * c
  let absRoot = sqrt(det |> abs)
  let root = if det < 0.0 then Complex(0.0, absRoot)
                          else Complex(absRoot, 0.0)
  let q = -(complex 0.5) * ((complex b) + (sign b |> float |> complex) * root)
  (q / complex(a), complex(c) / q)

Эээ, ну что я могу сказать? F# определенно превносит массу головной боли. Проблема тут как раз в том, что в C# нам доступны операторы автоконверсии вроде floatComplex, но в F# они попросту не работают! Поэтому вы можете либо определить их как функции (благо у них есть имя op_Implicit), либо же просто вызвать конструктор как делаю я.2

Заключение

То ли я чего-то недопонимаю, то ли F# действительно неудобен для работы с математикой? Ведь если нужно каждый int кастовать во float, а каждый float в Complex, и так далее, то это же сколько лишнего, никому не нужного и мешающего восприятию кода надо написать?

Критика welcome! А то может я что не так написал?

Заметки

  1. Напоминаю, что float в F# – это double в C#. А C#-ный float в F# называется float32.
  2. Тут еще хочется заметить что if который выбирает какой Complex создавать как бы не нужно — можно было вместо этого написать Complex.Sqrt(new Complex(b*b-4*a*c, 0.0)). Проблема только в том что в результате этого вычисления Real-значение будет чуточку отличаться от нуля. Например sqrt(Complex(-4.0, 0.0)) равен (1.22460635382238E-16, 2).

Written by Dmitri

5 февраля 2011 at 10:52

Опубликовано в .NET, C#, f#

Полезный набор Live Template’ов

9 комментариев

Я уверен, что многие из вас видели огромное количество open-source проектов, которые предлагают наборы сниппетов для Visual Studio. С одной стороны, конечно, подобные проекты – пустая трата времени обычного разработчика, т.к., говоря образно, к вам в монастырь приходит некто со своим уставом и диктует, что для счастливой и вечной жизни вам нужно таки заучить несколько комманд, которые вы потом будете использовать. Согласитесь, не глупо ли?

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

Предыстория

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

Идея была хорошая, но в результате получился трэш. И я бы рад забыть про эту идею не наткнись я, уже в 2010м году, на программу под названием CodeRush. Эта программа – плагин для производительности для Visual Studio – основной своей фичей мнит как раз реализацию удобых темплейтов которые позволяют… ну, по сути дела они делают то же что и обычные сниппеты для студии, но с одним капитальным различием.

В CodeRush используются только стандартные конструкции. Например, шорткаты для создания класса, метода, свойства. Написав c, создается класс. Написав ms, создается метод, который возвращает строку. А написав, например, pl.s, создается свойство типа List<string>.

Все подобные конструкты мы создаем каждый день в нашей работе. Это вещи, которые могут сэкономить массу времени людям которые, например, имея отличные знания C# не очень ориентируются в том как создавать типы на F#. И тут меня осенило. Ведь я, фактически, могу сам создать свой стандартный словарь сокращений для различных .Net-конструктов, выбрав те механизмы и типы, который лично я считаю наиболее удобными. Более того, я могу воспользоваться всей мощью мною любимого Решарпера, дабы иметь доступ к дополнительной, нужной мне информации – например к названию типа, в котором я сейчас нахожусь.

И понеслось…

Стандартный Словарь

Первым делом, мне пришлось выбрать те основные конструкты, которые будут доступны пользователю. Я выбрал следующие:

  • class

  • static class

  • interface

  • struct

  • enum

  • abstract class

  • Тестовый класс (с аттрибутом TestFixture и дефолтным методом с аттрибутом Test)

Что касается начинки класса, то мне понравились следующие элементы:

  • Private field

  • Private static field

  • Private readonly field

  • Public method

  • Test method (то же самое что и public method, но помечен как [Test])

  • Auto-property (другие типы свойств делать не захотелось т.к. это уже реализовано в Решарпере через рефакторинги)

  • Dependency property (то же самое что propdp в студии, только не надо писать лишний раз имя содержащего класса)

Теперь что касается типов. На вкус и цвет, как говориться, товарищей нет, поэтому в плане примитивных ValueType все может и понятно, а вот из reference types пришлось выбирать. В результате получилось вот это:

  • Примитивные типы: bool, char, float, byte, double, int, string, long, uint, Guid, DateTime

  • Более сложные типы: Exception, StringBuilder, List<T>, HashSet<T> (на самом деле не уверен что конкретно этот тип много кто использует), Dictionary<T,U>

Вполне очевидно, что пользователи могут предпочесть другие типы, но я не могу поддержать их все. Один тип, который я решил поддержать отдельно – это IEnumerable<T>. Помните я публиковал небольшой пост о возможных фичах C#5? Так вот, оттуда я вычерпнул идею превратить ~ (тильду) в аналог IEnumerable. На практике, это значит что написав m~s вы получите метод, который возвращает IEnumerable<string>.

Реализация

Первое, что я сделал – это взял типичный шаблон Решарпера и пропустил через XSD.EXE, получив объектное представление. После этого, сделал для каждой концепции массив, и поместил все во вложенные циклы: типы объектов (класс, метод, и т.д.), типы значения, различные вариации вроде массивов или Nullable.

Отдельный код с циклом создал и для дженериков, чтобы был вариант как задать тип руками List<MyType>, так и сделать “шаблоный” тип – например vl.s делает List<string>.

Остальное все дело техники. Создать XML-файл из готовых кусочков совсем несложно, на самом деле. Правда результат получился немаленький – аж 1Мб (на текущий момент) шаблонов, в количестве где-то 1200 штук. И это, понятное дело, еще не все – ведь будут новые идеи, захочется добавлять полезные фичи. Но это как пойдет.

Дайте Мне Скорее!

Скачать все темплейты можно тут. Чтобы их установить, сначала идем в меню ReSharper→Live Templates, потом в открывшемся окне нажимаем кнопочку Import, выбираем XML-файл. И ждем. Долго. Очень долго. Ведь шаблонов “за тыщу”. Ну а потом собственно пользуемся.

Вообщем, шаблон я выложил, но это work in progress, так что если что — пишите комментарии! Спасибо!


P.S. С Новым 2011м Годом! Я на самом деле не склонен подводить итоги, поэтому скажу кратко – я даже не 2010й год отдельно рассматриваю, а полосу 2008-2010, в которой, увы, было очень мало радостей – а точнее, почти не было. И хоть и хочется сказать что “кризис ушел”, на самом деле это, конечно же не так. Но не знаю как вам, а лично мне остается одно: работать, работать, работать.

Written by Dmitri

3 января 2011 at 3:41

Опубликовано в .NET, C#, ReSharper

Монадичность в сборке строк

19 комментариев

Когда-то давно я писал на тему того, как с помощью монадичного подхода можно делать цепочные проверки на null (монада Maybe). Сейчас я хочу показать аналогичный подход, в котором путем создания декоратора мы можем делать весьма интересные вещи со строковыми литералами.

Вы уже знаете что конкатенация большого количества строк не есть гуд, так? .Net рекоммендует нам использовать для этого StringBuilder, но тем не менее иногда мы все же опускаемся на землю и пользуемся операторами + или +=. Это нормально пока в дело не вступает какая-то сложная логика. Например:

void AppleReport(int count)
{
  Console.WriteLine("You have " + count + "apple" +
                    count != 1 ? "s" : string.Empty); // wtf?
}

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

Наличие или присутствие окончания “s” – это тоже по сути дела монадичное поведение, только не в той форме в которой мы привыкли. Первое приближение к решению – это написать, например, метод расширения AppendIf() для класса… StringBuilder:

public static StringBuilder AppendIf(this StringBuilder sb, string text, bool condition)
{
    if (condition)
        sb.Append(text);
    return sb;
}

Последующее использования сего конструкта достаточно предсказуемо:

void CountApples(int count)
{
    Console.WriteLine(
        new StringBuilder("You have ")
            .Append(count)
            .Append(" apple")
            .AppendIf("s", count != 1));
}

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

Монадичность + Гибкий Интерфейс

Итак, представим что у нас больше нет StringBuilder, но есть некий декоратор поверх него. Например:

public class MyStringBuilder
{
    private StringBuilder sb;
    public MyStringBuilder(string text = "")
    {
        sb = new StringBuilder(text);
    }
    public static implicit operator string(MyStringBuilder msb)
    {
        return msb.sb.ToString();
    }
}

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

Итак, допустим что у нас есть класс с методами расширения в которых фигурируют методы похожие на методы StringBuilder, только повешены они на строку (!!!). Например:

public static MyStringBuilder a(this string text, object moreText)
{
    var msb = new MyStringBuilder(text);
    return msb.a(moreText);
}

При этом, у самого MyStringBuilder есть метод a(), который реализован вот так:

public MyStringBuilder a(string text)
{
    sb.Append(text);
    return this;
}

В том же ключе можно, например, создать AppendIf() – опять же, чуть укоротив название метода:

public static MyStringBuilder ai(this string text, object moreText, bool condition)
{
    var msb = new MyStringBuilder(text);
    return msb.ai(moreText, condition);
}

Ну а теперь можно воспользоваться всем этим счастьем и начать собирать строки в более “монадичной” манере:

void CountApples(int count)
{
    Console.WriteLine("You have ".a(count).a(" apple").ai("s", count != 1));
}

По аналогии с этим подходом можно понастроить других полезных методов. Жаль лишь что нельзя совместить наш helper class и класс с методами расширения. Или можно – как вы думаете?

Written by Dmitri

24 ноября 2010 at 1:40

Опубликовано в .NET, C#