воскресенье, 9 декабря 2007 г.

Отмучался с Emit...

В предыдущем посте я описывал свой механизм проектирования по контракту. Никто ничего не откомментировал по его поводу, ну да ладно, не будем сейчас про это. Сегодня я закончил еще один этап своей работы, а именно генерирование прокси класса по определенному интерфейсу, который внутри себя использует класс ContractsManager, который в свое время внутри себя использует класс, который реализует необходимый интерфейс - ужас одним словом ;)
Попутно с этим, мне пришлось немного разобраться с таким пространством имен как System.Reflection.Emit. Страшное, надо сказать пространство имен ;)
С генерированием конструктора, который принимает параметр и запихивает его в специальное поле, проблем не возникло - примеров этого было много :) Ну попутно еще разобрался с тем, как сгенерировать поле.
Дальше началась проблема - необходимо было сделать метод, который реализует метод интерфейса следующим образом: внутри себя вызывает специальный метод у поля. Этому методу надо было передать имя метода и параметры метода. Ну так приблизительно я и сделал... Но прокси класс никак не хотел работать, со словами, что у него там какая то фигня сгенерирована, а не метод интерфейса :) Через некоторое время до меня наконец дошло, что это в C# ключевое слово params позволяет перечислять параметры через запятую... но потом они упаковываются в массив, а следовательно надо передавать не параметры, а массив параметров... И тут встала проблема - как создать локальную переменную в виде массива объектов, заполнить этот массив из параметров и передать куда нибудь... К счастью google помог и в этом :)
В итоге могу сказать - даже если плохо разбираешься в системном программировании .NET (имеется ввиду Emit, да и вообще программирование на чистом MSIL), то google лучший помошник. Хотя информацию приходиться агрегировать из примеров по маленьким кусочкам... Кстати говоря, ни нашел ни одного нормального источника на русском (да и английском), где можно было бы подробно почитать про Emit. Есть что то на intuit, но там на очень конкретном примере, в котором многое опускается...
P.S. А еще наконец то заменил себе старый монитор на новый ;)

воскресенье, 2 декабря 2007 г.

Проектирование по контракту - первые попытки

Сегодня я предложу вам один способ использования проектирования по контракту в .NET, точнее очередную библиотеку, автором которой и являюсь ;) Надеюсь, что вы напишите свои впечатления, замечания, пожелания и т.д. и т.п.
На сегодня, наверное, будет только один пример использования. Пример, который используется во многих статьях про проектирование по контракту - класс стек ;)
Сначала приведу код интерфейса - он довольно простой ;)
public interface IStack<T>
{
int Count { get; }

bool Empty { get; }

void Put(T item);

T
Remove();

T
GetItem();
}
Теперь можно сказать, какие предусловия, постусловия и инварианты можно придумать для данного интерфейса. Инвариантом может послужить условие о том, что количество элементов всегда больше или равно нулю. Предусловием для методов Remove и GetItem будет то, что количество элементов в стеке больше нуля. Постусловием для метода Put будет то, что свойство Count увеличивается на единицу, для Remove - уменьшается на единицу, для GetItem - свойство Count не изменяется.
По схеме, которую хочу предложить вам я, все утверждения записываются в отдельном классе (хотя их может быть и больше). Для каждого метода этого отдельного класса можно указать чем именно он является - предусловием, постусловием или инвариантом. Так же, необходимо произвести что то вроде mapping`а или биндинга параметров этих методов к параметрам проверяемого метода, а так же свойствам и полям. Все это делается через атрибуты.
Приведу пример проверяющего класса:
internal class StackCheck
{
[Invariant("Количество элементов больше или равно 0")]
public bool CheckCount(int Count)
{
return Count >= 0;
}

[Ensure("Количество элементов увеличивается на единицу", "Put")]
public bool EnsurePut(
[Old][PropName("Count")] int oldCount,
[PropName("Count")] int count)
{
return count == oldCount + 1;
}

[Ensure("Количество элементов уменьшается на единицу", "Remove")]
public bool EnsureRemove(
[Old][PropName("Count")] int oldCount,
[PropName("Count")] int count)
{
return count == oldCount - 1;
}

[Ensure("Количество элементов не изменяется", "GetItem")]
public bool EnsureItem(
[Old][PropName("Count")] int oldCount,
[PropName("Count")] int count)
{
return count == oldCount;
}

[Require("Стек не должен быть пустым", "GetItem")]
[Require("Стек не должен быть пустым", "Remove")]
public bool RequireNotEmpty([PropName("Count")] int count)
{
return count > 0;
}
}
Несложно заметить, что все проверочные методы должны возвращать bool, в противном случае этот метод будет опущен при проверке.
Атрибут Invariant задает, что метод является инвариантом класса, в данном случае такой метод один - CheckCount. В параметр конструктора атрибута можно передать описание инварианта. В случае, если инвариант не выполнится, то это сообщение будет использоваться в качестве сообщения ошибки, так же этот текст может быть использован для документации (подумываю позже написать что нибудь и для этого ;).
Перед параметром Count этого проверочного метода нет никакого атрибута, в таком случае для метода инварианта этот параметр будет пытаться биндиться к свойству проверяемого класса с соответствующим именем (регистр в данном случае важен), если такого свойства не будет, то к полю с таким названием, если не будет и поля, то сгенерируется исключение. В общем случае перед параметром проверочного метода инварианта можно указать один из двух атрибутов - PropName (биндится к свойству с указанным именем) или FieldName (биндится к полю с указанным именем).
Следующий метод помечен атрибутом Ensure. Этот атрибут принимает два параметра: описание (использование аналогично инварианту), а так же название метода, к которому относится постусловие. Кроме уже имеющихся возможных атрибутов параметров, в постусловиях можно использовать атрибут Old. Он говорит о том, что значение свойства (или чего то другого, в зависимости от второго параметра) должно браться до того, как был вызван проверяемый метод. Так же, кроме атрибутов биндинга к свойству и полю можно использовать атрибут биндинга к параметру метода - PropName. Если никакого атрибута не проставлено, то считается, что стоит атрибут PropName с названием аналогичному названию параметра. Сам метод EnsurePut проверяет, что количество элементов после вызова метода Put в проверочном классе будет увеличено ровно на единицу. Следующий метод - EnsureRemove, практически идентичен предыдущему, только он проверяет, что количество элементов уменьшилось на единицу после вызова метода Remove. Далее идет метод EnsureItem - он проверяет, что после вызова метода GetItem количество элементов не изменится.
Следующий метод помечен атрибутом Require (даже двумя ;) Параметры атрибута Require аналогичны атрибуту Ensure, только он говорит о том, что помеченый им метод является предусловием, а не постусловием. К параметрам метода предусловия могут применены все те же атрибуты, что и к методам Ensure, за исключением Old (он просто опускается и не учитывается). Как видно из кода, здесь проверяется, что перед вызовом методов Remove и GetItem количество элементов больше нуля.
В принципе, логичнее проверять не свойство Count, а свойство Empty. В таком случае код можно переписать так:
[Require("Стек не должен быть пустым", "GetItem")]
[Require("Стек не должен быть пустым", "Remove")]
public bool RequireNotEmpty([PropName("Empty")] bool empty)
{
return !empty;
}
Как можно заметить, сам класс помечен как internal. По крайней мере для рефлексии это имеет не такое большое значение ;)
Теперь, имея такой класс, мы можем сказать интерфейсу стека, что он должен использовать его в качестве проверочного - для этого служит атрибут ContractsType, которому необходимо передать тип проверяемого класса. Еще одно немаловажное свойство, в связи с этим, проверочный класс (в нашем случае StackCheck) должен иметь пустой публичный конструктор (хотя по поводу публичного - кто знает эту рефлексию ;)
[ContractsType(typeof(StackCheck))]
public interface IStack;
{
//Описание интерфейса...
}
Наверное теперь у вас возникает вопрос - как все это использовать? А использовать все это можно через специальный класс, который называется ContractsManager - он создается для экземпляра определенного класса. Например можно использовать вот так:
Stack<int> stack = new Stack();
ContractsManager contractsManager = new ContractsManager(stack);
int res = contractsManager.RunMethod<int>("Remove");
В данном примере мы создаем класс "обертку", которая может вызвать метод вместе с проверками. Заметьте, что используется generic метод - для того, что бы указать, результат какого типа вернет метод. Так же, обратите внимание на то, что здесь используется класс Stack - наследник интерфейса IStack - т.е. все проверки наследуются по иерархии.
Т.к. стек создается по умолчанию пустой, то в данном случае сгенерируется исключение - RequireException с текстом сообщения - "Стек не должен быть пустым".
Если необходимо вызвать метод без каких либо проверок, то можно просто вызвать метод у экземпляра класса. Но это еще не все. На самом деле у каждого из атрибутов (Invariant, Require и Ensure) имеется еще один параметр, который можно задать - это приоритет (по умолчанию он равен 0). У класса ContractsManager в свое время так же есть 3 свойства которые задают максимальные приоритеты для инвариантов, предусловий и постусловий (по умолчанию тоже равны 0). Соотвественно эти свойства можно менять во время исполнения программы тем самым "балансируя" производительностью.
Естественно, что все это пока только... бета ;) Уже сейчас есть некоторые проблемы, например с generic - скажем так, пока что я особо не обращаю на них внимание. Так же, есть еще несколько идей относительно того, что можно было бы еще добавить. В общем, жду так же и ваших комментариев ;)
P.S. Да, я очень не хороший мальчик, но библиотеку с кодом всего этого я пока никуда не выложил ;)

Pipes

Наткнулся сегодня на один интересный сервис. Служит он для построения лент новостей из уже имеющихся, причем имеется очень много интересных возможностей.
Например можно взять обычную ленту новостей и отфильтровать все ее записи по категориям или еще чему нибудь. Так же можно переименовать часть полей, можно сделать join нескольких лент, ну и всякого рода другие интересные возможности. В общем, советую попробовать хотя бы ради интереса ;)
Из недостатков пока нашел только то, что иногда русский язык отображается неправильно (но скорее всего это связано с неправильным указанием кодировки в самой ленте новостей).
Сам сервис предоставляет yahoo, но самое интересное, что при этом сервис использует некоторые API от google. В общем, идея, конечно интересная, посмотрим что будет дальше.