в

Kazan Dev Alliance

Казанское Сообщество Разработчиков Программного Обеспечения

Персональный блог Александра Шера

Professional blog for .Net programmers. Main topics: Microsoft ASP.NET AJAX, WPF/E, OOP, Refactoring

Знакомство со Script# - часть 2: полезные аттрибуты

Сразу оговорюсь. В этом посте и далее я буду говорить только о вариантах проектов Script#, совместимых с Microsoft AJAX Library. В основном, по двум причинам. Во-первых, мне Script# интересен именно как возможность создавать страницы и контролы ASP.NET, используя исключительно C#, в том числе с использованием пришедшего с серверной частью ASP.NET AJAX Extensions механизма дескрипторов, позволяющего отделять клиентский код от серверного. Во-вторых, версия, совместимая с Microsoft AJAX Library, более "лояльна" к существующему JS коду. В частности, в Microsoft AJAX Library все методы, расширяющие базовые типы JavaScript, сделаны статическими для обеспечения правильной работы циклов for..in в уже существующем коде.

Итак, сегодня речь пойдет об аттрибутах, которые предоставляет Script#.

[AttributeUsage(AttributeTargets.Delegate | AttributeTargets.Interface | AttributeTargets.Enum | AttributeTargets.Struct | AttributeTargets.Class)]
public sealed class ImportedAttribute

Главный (на мой взгляд) аттрибут расширения. Если задать его для некоторого типа (в том числе для перечисления или делегата), то компилятор не будет генерировать код этого типа для JS, полагая, что данный тип уже описан. Таким образом, с помощью этого аттрибута можно создавать прокси в Script# для любых уже существующих JS-типов. При использовании аттрибута тело у методов должно присутствовать, однако его вид значения не имеет - лишь бы компилировалось. Важно также отметить, что поскольку помеченные ImportedAttribute классы не компилируются в JS, в них разрешена перегрузка методов.

[AttributeUsage(AttributeTargets.Delegate | AttributeTargets.Interface | AttributeTargets.Enum | AttributeTargets.Class)]
public sealed class IgnoreNamespaceAttribute

Сообщает компилятору, что при генерации JS-кода, использующего помеченный тип (создание экземпляра, вызов статических методов и т.п.) не нужно прописывать пространство имен. Его удобно использовать вместе с ImportedAttribute для создания прокси для существующих классов, будь то базовые типы JS, DOM-элементы или пользовательские классы, написанные в "класическом" стиле. Также надо помнить, что если ImportedAttribute не задан и для типа генерится скрипт, то тип будет создан внутри указанного пространства имен, но при обращении к типу пространство имен будет игнорирваться.

[AttributeUsage(AttributeTargets.Property)]
public sealed class IntrinsicPropertyAttribute

Этот аттрибут означает, что к помеченному им свойству обращение будет как полю (без геттеров/сеттеров). IntrinsicPropertyAttribute применим только к свойствам типа, помеченного ImportedAttribute. По сути, данный аттирбут применим лишь при создании прокси для свойств базовых типов JS или свойств DOM-элементов.

[AttributeUsage(AttributeTargets.Class)]
public sealed class GlobalMethodsAttribute

Аттрибут предназначен для работы с глобальными функциями. Помеченный им класс должен быть объявлен как static, и не должен содержать ничего, кроме методов (естественно, статических). Если класс не помечен ImportedAttribute, то для всех методов будут сгенерированы глобальные функции (вне какого-либо пространства имен).

[AttributeUsage(AttributeTargets.Enum)]
public sealed class
FlagsAttribute

Обозначает перечесление как битовую маску. В Microsoft AJAX Library у всех перечислений есть булевое поле _flags. Если перечисление помечено FlagsAttribute, то _flags=true, иначе false. _flags влияет на результат работы метода toString() также как его аналог из FCL.

[AttributeUsage(AttributeTargets.Event | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method)]
public sealed class PreserveCaseAttribute

Как известно, в JS имена полей, методов и свойств начинаются с маленькой буквы (Сamel Case). В C# же принято, что имена методов и свойств начинаются с заглавной букы (Pascal Case). Поэтому по умолчанию компилятор Script# меняет имена методов, полей и свойств так, чтобы они соответствовали принятым в JS стандартам. Чтобы имя члена класса осталось неизменным, его нужно пометить аттрибутом PreserveCaseAttribute.

[AttributeUsage(AttributeTargets.Enum)]
public sealed class
NamedValuesAttribute

Перечисление, помеченное этим аттрибутом, воспринимается как набор имен. Вместо элементов перечисления используются их имена (в виде строковых значений). Само перечисление при этом нигде не используется, поэтому его можно пометить ImportedAttribute. Здесь не обошлось без печального недочета. System.Enum, от которого наследуются все перечисления, не имеет ни явной, ни неявной возможности приведения к string. Вызов меода ToString тоже "по умному" не обрабатывается. Единственная возмножнось - делать приведение через некоторый "промежуточный" тип.

[AttributeUsage(AttributeTargets.Enum)]
public sealed class
NumericValuesAttribute
Аналогичен NamedValuesAttribute, только вместо имен элементов перечисления подставляются соответствующие им числовые значения.

[AttributeUsage(AttributeTargets.Class)]
public sealed class RecordAttribute

Аттрибут предназначен для генерации "легковесных" классов. Это как сказано в описании аттрибута. А если быть точнее, то для генерации объектов без явного типа. Здесь проще показать на примере. Для некоторого класса A из пространства NS:

namespace NS {
    [Record]
    public
sealed class A {
        public int
i1;
        private int
 i2;
        public
 A (int i1_, int i2_) {
            i1 = i1_;
            i2 = i2_;
        }
    }
}

вместо класса в JS будет сгенерирован метод NS.$create_A:

NS.$create_A = function NS_A(i1_, i2_) {
    var
$o = { };
    $o.i1 = i1_;
    $o._i2 = i2_;

    return
$o;
}

Соответственно, там, где в коде создается новый экземпляр этого класса, вместо конструктора и опретора new будет подставляться вызов этого метода. Важно помнить, что в генерации участвуют только поля класса, инициализируемые в конструкторе. Все остальные поля, а также прочие члены класса, в генерации не участвуют. Более того, поскольку кроме полей и конструктора у класса ничего нет, то появляется ещё одно ограничение - все поля должны быть public. Единственное, на что нет ограничений, так это на тип полей. Поэтому можно запросто добавить в класс методы, используя делегаты.

Вот, собственно, и все. В aacorlib.dll есть ещё несколько аттрибутов, однако они либо предназначены для поддержания инфраструктуры Script#, либо пока что не работают. Поэтому о них я рассказывать не буду 8)

Комментарии

 

Raimon сказал:

interesting post!

July 9, 2007 11:10 AM
 

mika сказал:

Прикольная статейка. Пеши исщо.

>System.Enum, от которого наследуются все перечисления, не имеет ни явной, ни неявной возможности приведения к string.

Правда не ваша. Enum.ToString реализует эту функциональность.

August 3, 2007 5:11 AM
 

Aib сказал:

> Правда не ваша. Enum.ToString реализует эту функциональность.

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

Например, вот эта строчка кода:

string s = YesNoState.yes.ToString();

в JS будет выглядеть так:

var s = 'yes'.toString();

Согласитесь, хотелось бы видеть нечто иное.

August 5, 2007 7:53 AM
 

mika сказал:

>string s = YesNoState.yes.ToString();

Когда работаешь во Script# надо забыть о всех правильностях. Лови паттерн:

string s = (string)(object)YesNoState.yes;

И в JS будет так:

var s = 'yes';

August 30, 2007 9:41 AM
 

Aib сказал:

> Когда работаешь во Script# надо забыть о всех правильностях.

Я скажу больше - можно сделать класс, для которого будет рaелизовано неавное приведение его к String и Enum,

[Imported, IgnoreNamespace]
public sealed class EnumToString {
   private EnumToString() { }
   public static explicit operator EnumToString (ValueType value) {
       return (EnumToString)((object)value);
   }
   public static implicit operator string (EnumToString value) {
       return (string)((object)value);
   }
}

и тогда это будет выглядеть ещё короче:

string s = (EnumToString)YesNoState.yes;

и все же это не совсем то.

August 31, 2007 5:43 AM

Оставить комментарий

(required)  
(optional)
(required)  

© 2007 Kazan Developers Community and Post`s Authors