Головоломки на 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. Мартин Фаулер “Архитектура корпоративных приложений” (Идея данного паттерна изложена М. Фаулером в этой книге)

Обзор языка Boo! – как средства организации DSL Вашего приложения

boo-logo-128_med

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

В последнее время меня так и тянет насесть на какой-нибудь новенький язык программирования, особенно что-нибудь на типа Ruby или Python, но в связи с моей привязанностью среде разработки (Visual Studio, неверно это не очень хорошо – зависеть от IDE ;)) и затруднительной ситуацией с разработкой на ней на динамических языках (надеюсь это временное затруднение) от них пока пришлось отказаться. В то же время меня интересуют такие области как метапрограммирование и реализация DSL (Domain-Specific Language == проблемно-ориентированный язык приложения), и в этой связи видится мне, что Boo является весьма интересным языком. К тому же Boo — язык CLR, что очень даже не плохо для изучения.

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

Что такое Boo?

Boo представляет собой open-source объектно-ориентированный статически типизированный язык программирования для CLR с Python’о-подобным синтаксисом, язык увидел свет в 2004 году из-под рук Rodrigo B. de Oliveira. В настоящее время последняя версия 0.8.2, но даже если нет еще версии 1.0, это действительно очень стабильной и зрелый язык, который можно использовать на производстве.

Основные концепции в Boo:

  1. Дать компилятору сделать большую часть работы, для того чтобы помочь языку выглядеть более динамическим с автоматическим объявлением переменных, выводом и приведением типов.
  2. В то же время он предоставляет возможности для выполнения статической компиляции, проверку ошибок во время компиляции.
  3. Boo также предоставляет функциональную составляющую в том, что функции могут использоваться в качестве возвращаемого значения, как аргументы, хранимые в переменных, а так же как объекты.
  4. Boo имеет первоклассные генераторы, это конструкций, способные производить более одного значения при использовании итерации цикла (“return a for a in apples if a.IsRipe”).
  5. Boo также поддерживает утиную типизацию (Duck Typing), которая позволяет отказаться от обеспечения безопасности статических типов и принимать любой объект, который просто имеет соответствующие методы и свойства, определяемые во время выполнения.
  6. Boo также имеет расширения методов и свойств (extension methods and extension properties).

Одним из наиболее интересных функций Boo является расширяемость процесса компиляции, которая позволяет управлять компиляцией и изменять или добавлять функции. Можно добавить подпрограмм для проверки конвенции кода, генерирования отчётов или преобразовывания кода. То, что Вы сможете сделать в C# с помощью тулзы посткомпиляции такой, как PostSharp и только ограниченный набор действий. В Boo Вы могли бы например автоматически преобразовать класс в одноэкземплярный (pattern Singleton), просто добавив атрибут следующим образом:

[singleton]
class MySingleton: pass

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

В Boo пробелы имеют важное значение как в python, но есть возможность включить режим “Whitespace Agnostic”, позволяющий оканчивать выражение с помощью “end”, который избавит Вас от возможных проблем с пробелами.

Что нужно для начала работы с Boo?

Главным образом если Вы хотите узнать Boo, достаточно иметь последнюю версию Boo . В папке bin, Вы найдете файл “Booish.exe”, являющийся интерпретатором командной строки Boo, которая позволяет проиграться с синтаксисом языка.image

Для разработки нам необходима IDE, и следовательно у нас два варианта.

Большинство Boo разработчиков работают с SharpDevelop , и если Вы скачаете последнюю версию, в ней будет Boo полностью готовый к использованию. Эта IDE имеет преимущество, с точки зрения создания DSL, так как в ней легче добавить функции автозавершения кода и подсветки синтаксиса, в Visual Studio же потребуются написать лексер и парсер; в SharpDevelop можно сделать это с помощью XML-файла.

Но с другой стороны Visual Studio является зрелой IDE, и с BooLangStudio вы получите Boo интегрированный в Visual Studio. Люди жалуются, что проект является нестабильным и это лишь альфа выпуск, так же я встретился в нём с проблемами уникода (UTF8), которые пока не смог разрешить, но у меня пока нет весомых замечаний.

В настоящее время я буду пользоваться Visual Studio в силу привычки, но я могу изменить свой взгляд при продолжении этой статьи.

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

Продолжение следует…


Лоуренс Морони "Введение в Silverlight2"

image Ребята из команды Microsoft Россия подготовили нам новогодний подарок, и перевели книжку Лоуренса Морони «Введение в Silverlight2». Скачать её можно здесь.

Думаю стоит прочитать её, освежить свои знания по SL, тем более в следующем году предстоит с ним плотненько поработать.


Clone Detective for Visual Studio

Clone Detective

Утилита для поиска копипастов в коде.

Доступна на проекте Codeplex.


Musthave: NArrange — органайзер кода для .NET

Отличная внешняя утилита (не интегрируется с VS), позволяющая довольно таки быстро и корректно (~90% ;)) привести код приложения к формализму, описаному в конфигурационном файле, что является полезным приобритением для разработчиков, работающих в команде.

Скачать. (на данный момент последняя версия 0.2.7)

Подробности по установке и настройке.


.net in samples

http://www.skilldrive.com/book/DOTNETinSamples.htm

Отличная статья с практическими примерами.


Lutz Roeder pt. 2

Теперь рефлектор сменил хозяина, инфа тут.


Lutz Roeder

http://www.lutzroeder.com/dotnet/

Мастхев для любого .net девелопера.

Тут есть рефлектор и ресорсер, оч. полезные утилиты.


Подсветка кода здесь

http://source.virtser.net/default.aspx

идём по линку