среда, 27 августа 2008 г.

Type Forwarding и не только

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

Можно, конечно, и так. Но лень :) Тут-то нам и приходит на помощь Type Forwarding!

Type Forwarding позволяет в нашей главной сборке указать, что нужный нам класс находится в другой .dll, при этом все остальные сборки по-прежнему будут считать, что наш класс объявлен в главной сборке.

Показываю на пальцах:

  • Создаем два проекта MainDll и SecondDll
  • Добавляем в список зависимостей SecondDll в проекте MainDll
  • Объявляем какой-нибудь класс MyClass в SecondDll
  • В MainDll добавляем такой код в файл AssemblyInfo.cs:
    [assembly:TypeForwardedToAttribute(typeof(MyClass))] 
  • Компилируем всё что получилось
А теперь создаем какой-нибудь проект и добавляем в список его зависимостей ТОЛЬКО MainDll.dll и добавляем код вида:
MyClass mc = new MyClass();

Компилируем, запускаем, любуемся :)

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

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

Вы спросите - зачем? А затем, что если вдруг понадобится сменить библиотеку на другую, которая меньше глючит или обладает большей функциональностью - изменения в проекте коснутся только нашего прокси-класса, а не всего-всего проекта вообще.

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

Более того, похожий подход я применил и при работе с медиабиблиотеками iTunes и Windows Media Player. Даже несмотря на то, что часть функций (в частности поиск по не полному имени артиста/песни) в медиабиблиотеке Windows Media Player не работала.

суббота, 16 августа 2008 г.

Еще немного про Enum

Вот здесь можно прочитать как научить PropertyGrid отображать "человеческие" названия Enum'ов в выпадающих списках. Не так чтобы уж совсем просто, но работает :)

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

Зарелизилось!

Вышли .NET Framework 3.5 SP1 ("всего" 231 Мб) and Visual Studio 2008 SP1 (831 Мб). Вообщем, скромно, учитывая размеры СП1 к Висте :)

суббота, 9 августа 2008 г.

Вывод "человеческих" имен Enum с помощью Extension Metods

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

Сегодня, вооружась новыми технологиями, я расскажу как можно сделать то же самое используя методы-расширения (Extension Metods).

Во-первых, нам понадобится следующий код:

[AttributeUsage(AttributeTargets.Field)]
public class EnumDescriptionAttribute: Attribute
{
    public readonly string description;

    public EnumDescriptionAttribute(string description)
    {
        this.description = description;
    }
}

public enum etest
{
    [EnumNameAttribute("Значение 1")]
    value1,
    [EnumNameAttribute("Значение 2")]
    value2,
    …
}
Здесь мы определяем атрибут, который отвечает за человеческие имена элементов перечисления и, собственно, само перечисление, которое мы и будем выводить пользователю. Нам понадобится вот такой класс:


public static class Specials
{
        public static string GetHumanName(this Enum p)
        {
            return ((EnumNameAttribute)p.GetType().GetField(p.ToString()).GetCustomAttributes(typeof(EnumNameAttribute), true)[0]).name;
        }
}
И теперь в нашем коде мы можем писать вот так:

Console.WriteLine(etest.value1.GetHumanName());
что на мой взгляд намного удобнее кода вида:

Console.WriteLine(Specials.GetHumanName(etest.value1));
Хотя, конечно, без "Правая кнопка мыши->Go To Definition" понять откуда у Enum'ов взялась функция GetHumanName будет сложновато :)

среда, 6 августа 2008 г.

Extension methods (методы-расширения)

Одной из очень интересных фич .Net Framework 3.0 являются extension methods (методы-расширения).

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

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


Следует заметить, что первый вариант недоступен в случае sealed-класса.

В C# 3.0 появляется возможность писать статические методы, но вызывать их как методы, принадлежащие исходному классу:

static class MyDateTimeExtensions
{
        public static string ToMyDate(this DateTime date)
        {
            return date.AddDays(1).ToLongDateString();
        }
}
Вызов будет, например, таким:

MessageBox.Show(DateTime.Now.ToMyDate());

Всё это, конечно, хорошо... Кроме того, что хочется использовать такую же функциональность в старых C# 2.0 проектах. Однако компилятор матерится и не компилирует. Но хочется. А как известно - если чего-то нельзя, но очень хочется - значит можно :)

Как оказалось - использование extension methods возможно после применения небольшого "хака" :) В проект необходимо добавить следующий код:

namespace System.Runtime.CompilerServices  
{  
  [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]  
  public class ExtensionAttribute : Attribute  
  {  
  }  
}

После чего приведенный выше пример начнет успешно компилироваться и - самое главное! - даже работать в проекте C# 2.0.