6й подкаст Петербургской группы ALT.NET

Сообщества Разработчиков

Ведущие: Дмитрий Нестерук twitter и Виталий Баум twitter

Наши гости: Владимир Юнев и Евгений Жарков

Сообщества в рунете

За пределами рунета

Подкасты Петерубргской Группы Alt.Net

Подкасты Петерубргской Группы Alt.Net

Подкасты о разработке в среде .Net. Ключевые слова: C#, F#, Boo, Visual Studio, .Net, PostSharp, Asp.Net личная подкаст-лента Петербургская Группа Alt.NetПетербургская Группа Alt.Net (подробнее, RSS-поток)


Совместный подкаст spbalt.net и csharpus

Встречались:

http://spbalt.net : butaji и dnesteruk

http://csharpus.com/ : dimapasko и tihobrazov

Холиварим, .NET и аналоги:

http://highscalability.com/blog/2009/8/5/stack-overflow-architecture.html

http://stackoverflow.com/questions/791447/windows-azure-vs-amazon-ec2-vs-google-app-engine

 

Обсуждение новых фич .NET 4.0, в частности:

 

Подробнее о подкастах:

Подкасты Петерубргской Группы Alt.Net
Подкасты Петерубргской Группы Alt.Net

Подкасты о разработке в среде .Net. Ключевые слова: C#, F#, Boo, Visual Studio, .Net, PostSharp, Asp.Net
личная подкаст-лента
Петербургская Группа Alt.NetПетербургская Группа Alt.Net (подробнее, RSS-поток)

 
csharpus podcast

Подкаст о разработке на платформе .NET
личная подкаст-лента
Dima PaskoDima Pasko (подробнее, RSS-поток)


Выкладываю выступления нашей ALT.NET группы на Vimeo


Building DSL’s on Microsoft .NET CLR and DLR from Vitaly Baum on Vimeo.

DLR Hosting from Vitaly Baum on Vimeo.


Языки предметной области Domain-Specific Languages (DSL)

Что это?

Это некоторая форма компьютерных языков, разрабатываемых для специфичной предметной области. Это то, что позволяет вам (разработчикам ПО) лучше взаимодействовать с носителями “доменных знаний”. А так же позволяет более лаконично оформлять бизнес-логику. Это то, что представляет собой, к примеру, SQL, Linq, многое из синтаксиса Ruby On Rails.

Зачем мне это?

Если вы согласны с утверждением: “Языки общего назначения порой слишком красноречивы”, вы разрабатываете на .NET, либо сильно интересуетесь программированием, то наш доклад будет вам интересен.

Что я узнаю?

Ответы на следующие вопросы:

  • Что такое DSL?
  • Откуда это понятие пришло к нам?
  • Какие бывают DSL?
  • Какие “языки общего употребления (GPL)”  предоставляют возможности построения DSL? Какие из них есть на .NET?
  • Почему я должен использовать DSL? Какие плюсы от этого?
  • Какие шаблоны используются при построении DSL?
  • А можно увидеть примеры?

Материалы нашего выступления

Слайды презентации

Building DSLs on CLR and DLR (.NET)

Видео:

http://video.yandex.ru/users/thecoffee/collection/1/

Видео в более пригодном к рассматриванию надписей на доске качестве можно слить по ссылкам ниже:

http://narod.ru/disk/9278634000/01.wmv.html

http://narod.ru/disk/9279885000/02.wmv.html

Все рассмотренные примеры доступны здесь:

http://spbalt.net/Content/Baum_Moiseev_DSL.zip


Головоломки на C# (Ответы)

Введение

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

Ответы

Далее я продублирую приведенные фрагменты кода, а так же дополню их описанием нюансов

  1. Перегрузка

    Как вы думаете что выведет данный код и почему?

    using System;
    
    class Base
    {
        public virtual void Foo(int x)
        {
            Console.WriteLine ("Base.Foo(int)");
        }
    }
    
    class Derived : Base
    {
        public override void Foo(int x)
        {
            Console.WriteLine ("Derived.Foo(int)");
        }
        
        public void Foo(object o)
        {
            Console.WriteLine ("Derived.Foo(object)");
        }
    }
    
    class Test
    {
        static void Main()
        {
            Derived d = new Derived();
            int i = 10;
            d.Foo(i);
        }
    }

    Результат: Derived.Foo(object).Мы получили его таким, т.к. при выборе подходящего перегруженного метода в наследуемом типе компилятор игнорирует определенный в базовом типе метод, даже если он был переопределен в наследуемом!

  2. Порядок! Порядок!

    В каком порядке напечатаются строки на экране?

    using System;
    
    class Foo
    {
        static Foo()
        {
            Console.WriteLine ("Foo");
        }
    }
    
    class Bar
    {
        static int i = Init();
        
        static int Init()
        {
            Console.WriteLine("Bar");
            return 0;
        }
    }
    
    class Test
    {
        static void Main()
        {
            Foo f = new Foo();
            Bar b = new Bar();
        }
    } 

    Результат: В варианте Джона он получает следующую последовательность: “Bar, Foo”, однако при воспроизведении на своей машине у меня получилось: “Foo, Bar”. Я думаю, что причина различий лежит в разнице версий исполняющей среды (CLR, я исполнял код на версии 2.0.50727.3053). Он объясняет свои результаты тем, что у Foo конструктор статический, следовательно он не будет выполнен до точки первой инициализации этого класса. У Bar же нет статического конструктора, что позволяет CLR инициализировать его раньше. Однако, нет гарантии, что Bar будет напечатан вовсе, так как в нашем примере у CLR в принципе нет надобности инициализировать это статическое поле. Более подробно все эти нюансы описаны здесь!

  3. “Глупая” арифметика

    Компьютеры призваны помочь нам с арифметическими вычислениями, но почему тогда это выражение выводит false?

    double d1 = 1.000001;
    double d2 = 0.000001;
    Console.WriteLine((d1-d2)==1.0); 

    А такой вариант?

    double sum = 0;
    for (int i =0;i<10 ;i++ )
    {
      sum+=0.1;    
    }
    Console.WriteLine(sum==1.0); 

    Результат: Все значения в данных примерах хранятся в двоичном виде, хотя 1.0 может хранится как точное значение, 1.000001 будет храниться примерно как 1.0000009999999999177333620536956004798412322998046875, и 0.000001 так же будет сохранено в виде 0.000000999999999999999954748111825886258685613938723690807819366455078125. В связи с этим нельзя утверждать о равенстве в предыдущих примерах. Почитать подробнее о числах с плавающей точкой.

    На заметку: В связи с указанными выше особенностями работы с типами с плавающей точкой, однозначно рекомендуют не использовать их при работе с денежными единицами, в данном случае рекомендуют определять собственные денежные типы (так называемые типы объект-значение [Object-Value]) с собственной логикой округления и прочих арифметических операций, а так же межвалютных операций.

  4. Печать, печать, печать

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

    using System;
    using System.Collections.Generic;
    
    class Test
    {
        delegate void Printer();
        
        static void Main()
        {
            List<Printer> printers = new List<Printer>();
            for (int i=0; i < 10; i++)
            {
                printers.Add(delegate { Console.WriteLine(i); });
            }
            
            foreach (Printer printer in printers)
            {
                printer();
            }
        }
    } 

    Результат: О эта радость от фиксированных переменных. В данном примере всего одна переменная и её значение изменяется при каждой итерации в цикле. Анонимные методы фиксируют переменную, а не её значение, следовательно в результате мы получим десть раз число 10 на выводе!

  5. Ничего странного с компилятором тут не случилось

    Этот код скомпилится? Как это? Что бы это значило?

    using System;
    
    class Test
    {
        enum Foo { Bar, Baz };
        
        static void Main()
        {
            Foo f = 0.0;
            Console.WriteLine(f);
        }
    }

    Результат: Это не должно компилироваться, однако это происходит. Это не должно копилироваться, в связи с тем, что только значение 0 может быть конвертированно в значение по умолчанию для enum. В примере же значение 0.0 имеет место быть корректным в связи с небольшой недоработкой компилятора. В результате будет напечатано Bar, т.к. 0 будет значением Foo.

    using System;
    
    class Test
    {
        enum Foo { Bar, Baz };
        
        const int One = 1;
        const int Une = 1;
        
        static void Main()
        {
            Foo f = One-Une;
            Console.WriteLine(f);
        }
    } 

    Результат: Данный пример не будет компилироваться под C#2.0, но прекрасно компилируется под C#3.0. Это известная особенность, объяснить это можно тем, что оптимизированный алгоритм выполняет вычисления значения One-Une раньше, а следовательно перечисление даёт своё значение по-умолчанию 0.

  6. Вывод типа

    Какой же вариант будет выведен на экран?

    using System;
    
    class Test
    {
        static void Main()
        {
            Foo("Hello");
        }
        
        static void Foo(object x)
        {
            Console.WriteLine("object");
        }
        
        static void Foo<T>(params T[] x)
        {
            Console.WriteLine("params T[]");
        }
    } 

    Результат: params T[] будет напечатан. Но почему же компилятор выбирает создание массива, хотя в этом нет надобности? Этому есть две причины. Во-первых при выборе перегруженных методов T будет рассматриваться как System.String. Ничего страшного пока в этом нет.

    Однако при выборе “лучшего” метода среди параметров string x and params string[],x формально будет приоритетнее. Однако с нашей точки зрения эффективности выбор между object x и params string[] x падёт на метод с params string[] x в связи с тем, что он не требует преобразований типов.

Материалы

 

  1. Подготовлено по материалам http://www.yoda.arachsys.com/csharp/teasers-answers.html
  2. А так же http://martinfowler.com/

Головоломки на C#

Вступление

 

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

В данной статье я хочу привести несколько примеров немного неожиданного поведения конструкций на C#.

Примеры

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

  1. Перегрузка

    Как вы думаете что выведет данный код и почему?

    using System;
    class Base
    {
    public virtual void Foo(int x)
        {
            Console.WriteLine ("Base.Foo(int)");
        }
    }
    class Derived : Base
    {
    public override void Foo(int x)
        {
            Console.WriteLine ("Derived.Foo(int)");
        }
    public void Foo(object o)
        {
            Console.WriteLine ("Derived.Foo(object)");
        }
    }
    class Test
    {
    static void Main()
        {
            Derived d = new Derived();
    int i = 10;
            d.Foo(i);
        }
    }
  2. Порядок! Порядок!

    В каком порядке напечатаются строки на экране?

    using System;
    class Foo
    {
    static Foo()
        {
            Console.WriteLine ("Foo");
        }
    }
    class Bar
    {
    static int i = Init();
    static int Init()
        {
            Console.WriteLine("Bar");
    return 0;
        }
    }
    class Test
    {
    static void Main()
        {
            Foo f = new Foo();
            Bar b = new Bar();
        }
    }
  3. “Глупая” арифметика

    Компьютеры призваны помочь нам с арифметическими вычислениями, но почему тогда это выражение выводит false?

    double d1 = 1.000001;
    double d2 = 0.000001;
    Console.WriteLine((d1-d2)==1.0);


    А такой вариант?

    double sum = 0;
    for (int i =0;i<10 ;i++ )
    {
      sum+=0.1;
    }
    Console.WriteLine(sum==1.0);
  4. Печать, печать, печать

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

    using System;
    using System.Collections.Generic;
    class Test
    {
    delegate void Printer();
    static void Main()
        {
            List<Printer> printers = new List<Printer>();
    for (int i=0; i < 10; i++)
            {
                printers.Add(delegate { Console.WriteLine(i); });
            }
    foreach (Printer printer in printers)
            {
                printer();
            }
        }
    }
  5. Ничего странного с компилятором тут не случилось

    Этот код скомпилится? Как это? Что бы это значило?

    using System;
    class Test
    {
    enum Foo { Bar, Baz };
    static void Main()
        {
            Foo f = 0.0;
            Console.WriteLine(f);
        }
    }


    А как Вам такой вариант?

    using System;
    class Test
    {
    enum Foo { Bar, Baz };
    const int One = 1;
    const int Une = 1;
    static void Main()
        {
            Foo f = One-Une;
            Console.WriteLine(f);
        }
    }
  6. Вывод типа

    Какой же вариант будет выведен на экран?

    using System;
    class Test
    {
    static void Main()
        {
            Foo("Hello");
        }
    static void Foo(object x)
        {
            Console.WriteLine("object");
        }
    static void Foo<T>(params T[] x)
        {
            Console.WriteLine("params T[]");
        }
    }

Материалы

Подготовлено по материалам http://www.yoda.arachsys.com/csharp/teasers.html

Ответы на поставленные задачи будут в следующем посте.


Lazy Computation in C# (Ленивые вычисления в C#)

Немного теории.

Большинство современных языков разработки, используемых на практике (таких как C#, VB.NET, C++, Python и Java) реализуют так называемые немедленные вычисления, это означает, что операция выполняется, так только становятся известны значения её операндов. Однако, ясно, что немедленное вычисление многих функций не всегда необходимо и рационально с точки зрения производительности, поэтому само собой напрашивается решение, позволяющее отложить эти вычисления на тот момент, когда они нам будут действительно нужны.

Мартин Фаулер в свой книге PoEAA вводит понятие паттерна Lazy Load (загрузка по требованию, ленивая загрузка), суть которого состоит в том, что объект не содержит данные, но знает где их взять, если они ему станут нужны. Это как раз то, о чём мы и ведем речь, следовательно воспользуемся этим шаблоном.

Реализовать данный шаблон можно несколькими различными вариантами:

  1. Lazy Initialization – Инициализация по требованию. Это самый простой способ – реализовать проверку поля на null и в случае необходимости заполнять его данными.
  2. Virtual Proxy – Виртуальный прокси-объект. Метод несколько усложнен проблемой идентификации объектов, т.к. вместо них, до инициализации, выступают заменители.
  3. Ghost – Фиктивный объект, Призрак. Это реальный объект с неполным состоянием.
  4. Value Holder – Диспетчер значения. Объект является оболочкой для некоторого значения. Так же не самый лучший вариант в связи с проблемами типизации.

Примеры реализации.

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

Мы напишем обобщенный класс Lazy<T>, который будет представлять загрузку по требованию, а так же кэшировать результат вычислений для дальнейших обращений к ним.

using System.Linq;
public class Lazy<T> {
private Func<T> func;
private T result;
private bool hasValue;
public Lazy(Func<T> func) {
this.func = func;
this.hasValue = false;
  }
public T Value {
get {
if (!this.hasValue) {
this.result = this.func();
this.hasValue = true;
      }
return this.result;
    }
  }
}

Данный класс имеет три поля:

  • func – делегат Func<T> инкапсулирующего передаваемый метод (находится в пространстве имён Linq, используется для инкапсуляции метода без параметров);
  • result – поле для хранения результата вычислений;
  • hasValue – флаг для обозначния, производились ли уже вычисления.

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

Lazy<int> lazy = new Lazy<int>(() => {
    Console.WriteLine("calculating...");
return 42;
  });
Console.WriteLine("starting...");
Console.WriteLine("result = {0}", lazy.Value);
Console.WriteLine("result (again) = {0}", lazy.Value);

Результат работы программы:

starting…

calculating…

result = 42

result (again) = 42

Мы наглядно видим, как в поле func заносится лямба-выражение, результат которого выводится после вызова свойства Value. Причём повторный вызов свойства выводит кэшированные данные.

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

public static class Lazy {
public static Lazy<T> New<T>(Func<T> func) {
return new Lazy<T>(func);
  }
}

Будет он выглядеть примерно так. Кстати, примерно также выглядит System.Nullable (один из стандартных классов .NET).

Используя класс Lazy мы можем создать экземпляр нашего типа, вызвав метод Lazy.New вместо написания new Lazy<int> к примеру. Для ещё пущего повышения наглядности будем использовать атрибут var.

int a = 22, b = 20;
var lazy = Lazy.New(() => {
    Console.WriteLine("calculating...");
return new { Mul = a*b, Sum = a+b };
  });
Console.WriteLine("Mul = {0}, Sum = {1}",
  lazy.Value.Mul, lazy.Value.Sum);

Все наши приобритения продемонстрированы Выше в примере, расчитывающий сумму и произведения двух чисел.

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

static Random rnd = new Random();
static void UseRandomArgument(Lazy<int> a0, Lazy<int> a1) {
int res;
if (rnd.Next(2) == 0)
    res = a0.Value;
else
    res = a1.Value;
  Console.WriteLine("result = {0}", res);
}

Как хорошо видно в данном примере, один из аргументов метода может не использоваться вовсе.

var calc1 = Lazy.New(() => {
    Console.WriteLine("Calculating #1");
return 42;
  });
var calc2 = Lazy.New(() => {
    Console.WriteLine("Calculating #2");
return 44;
  });
UseRandomArgument(calc1, calc2);
UseRandomArgument(calc1, calc2);

Напишем обработчик для данного метода и посмотрим, какие из аргументов будут проинициализированы:

Calculating #1
Result = 42
Result = 42

Повторим запуск приложения:

Calculating #1
Result = 42
Calculating #2
Result = 44

Пример: Список шрифтов с предосмотром.

 

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

Наш класс для хранения информации о шрифтах будет следующим:

private class FontInfo {
public Lazy<Bitmap> Preview { get; set; }
public string Name { get; set; }
}

Метод для генерации и отрисовки изображения шрифта:

private void DrawFontPreview(FontFamily f, Bitmap bmp) {
  Rectangle rc = new Rectangle(0, 0, 300, 200);
  StringFormat sf = new StringFormat();
  sf.Alignment = StringAlignment.Center;
  sf.LineAlignment = StringAlignment.Center;
string lipsum = "Lorem ipsum dolor sit amet, consectetuer " +
    "adipiscing elit. Etiam ut mi blandit turpis euismod malesuada. " +
    "Mauris congue pede vitae lectus. Ut faucibus dignissim diam. ";
using (Font fnt = new Font(f, 15, FontStyle.Regular))
using (Graphics gr = Graphics.FromImage(bmp)) {
    gr.FillRectangle(Brushes.White, rc);
    gr.DrawString(lipsum, fnt, Brushes.Black, rc, sf);
  }
}

На загрузке формы нашего приложения связываем наши значения с методом выбора шрифта из списка.

private void FontForm_Load(object sender, EventArgs e) {
  var fontInfo = FontFamily.Families.Select(f => {
// Создаем значение по требованию для картинки
      var preview = Lazy.New(() => {
          Bitmap bmp = new Bitmap(300, 200);
          DrawFontPreview(f, bmp);
return bmp;
        });
// Возвращаем шрифт с названием и превьюшкой
      return new FontInfo { Name = f.Name, Preview = preview };
    });
// Используем дата-байдинг для заполнения списка
  fontCombo.DataSource = fontInfo.ToList();
  fontCombo.DisplayMember = "Name";
}

При изменении выбранного шрифта перерисовываем изображение:

private void fontCombo_SelectedIndexChanged(object sender, EventArgs e) {
  FontInfo fnt = (FontInfo)fontCombo.SelectedItem;
  fontPreview.Image = fnt.Preview.Value;
}

Заключение

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

Материалы

 

  1. http://msdn.microsoft.com/en-us/vcsharp/bb870976.aspx (При написании данной статьи я руководствовался данной работой, все рассмотренные примеры, взяты там же)
  2. Мартин Фаулер “Архитектура корпоративных приложений” (Идея данного паттерна изложена М. Фаулером в этой книге)