в

Kazan Dev Alliance

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

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

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

July 2007 - Posts

  • Знакомство со Script# - часть 3: используем foreach и создаем свои события

    Первые попытки использовать выражение foreach в Script# могут привести к генерации JS-скрипта, который не будет работать. Кроме того, в документации указано, что цикл foreach нельзя использовать с массивами, поскольку они не реализуют интерфейс IEnumerable. Тем не менее, реализация foreach в Script# полностью покрывает функционал выражения for..in в JS. Нужно лишь правильно им пользоваться.

    Итак, сперва рассмотрим различия между конструкциями foreach в С# (ведь Script# - это просто компилятор для C#) и for..in в JS.

    Во-первых, коду на C#

    foreach (object obj in col) {

        object val = obj;

    }

    соответствует код на JS

    for (var key in col) {

        var val = col[key];

    }

    Т.е., если в C# foreach перечисляет элементы коллекции, то в JS for..in перечисляет строковые ключи.

    Во-вторых, foreach в C# может работать только с объектами, реализующими интерфейс IEnumerable, а for..in в JS позволяет пройтись по всем пользовательским членам экземпляра класса.

    Теперь разберемся с тем, как for..in в JS работает с массивами. Вот небольшой пример JS-кода:

    var a = [1, 2, 3];

    var s1 = 0;

    var s2 = 0;

    for (var i = 0; i < a.length; i++) {

        s1 += i;

    }

    for (var i in a) {

        s2 += i;

    }

    alert("sum1: "+s1);

    alert("sum2: "+s2);

    Результат его выполнения, возможно, кого-то удивит. По идее, s1 и s2 должны быть равны. Однако, в первом случае i – это число, а во втором – строка. Таким образом, внутри конструкции for..in массив (так же как и любой другой JS-объект) воспринимается как словарь, для которого ключом является строка (а не число).

    Учитывая все вышенаписанное, принципы использования foreach в Script# выглядят вполне логичными.

    В пространстве имен System есть класс Dictionary, который реализует интерфейс IEnumerable, что позволяет использовать его в foreach, а также содержит статический метод GetDictionary, с помощью которого можно получить «Dictionary-обертку» для любого объекта. При генерации JS-кода он преобразуется в Object, так что никаких отдельных классов не создается.

    В качестве элемента Dictionary выступает тип DictionaryEntry, почти что точная копия своего «однофамильца» из FCL, с тем лишь отличием, что свойство Key имеет тип string, а не object:

    [imported]

    public sealed class DictionaryEntry {

        internal DictionaryEntry();

     

        [IntrinsicProperty]

        public string Key { get; }

        [IntrinsicProperty]

        public object Value { get; }
    }

    В результате, код на Script#:

    foreach (DictionaryEntry entry in Dictionary.GetDictionary(obj)) {

        string key = entry.Key;

        object value = entry.Value;

    }

    будет преобразован в такой JS-код:

    var $dict1 = obj;

    for (var $key2 in $dict1) {

        var entry = { key: $key2, value: $dict1[$key2] };

        var key = entry.key;

        var value = entry.value;
    }

    Таким образом, использование foreach вместе с классами Dictionary и DictionaryEntry полностью покрывает функционал цикла for..in.

    Теперь перейдем ко второму вопросу – создание собственных событий. Как уже было сказано ранее, из-за необходимости обеспечивать обратную совместимость, к типу Object не было добавлено никаких полей и методов, кроме статических. Т.е. информацию о событиях хранить по умолчанию негде. Поэтому, чтобы создать свое событие, нужно сперва добавить поле-хранилище. Для этого в MS AJAX Library существует тип Sys.EventHandlerList:

    using Sys;

    namespace Samples {

        public class ClassWithEvent {

            private EventHandlerList events;

    Теперь мы можем определить событие:

            private const string SampleEventKey = "EventKey";

            public event EventHandler SampleEvent {

                add {

                    events.AddHandler(SampleEventKey, value);

                }

                remove {

                    events.RemoveHandler(SampleEventKey, value);

                }
            }

    и метод для его вызова:

            protected virtual void OnSampleEvent (EventArgs e) {

                EventHandler ev = (EventHandler)events.GetHandler(SampleEventKey);

                if (ev != null) {

                    ev(this, e);
                }
            }

    Естественно, вместо EventArgs и EventHandler можно использовать любые другие классы, в том числе и свои (при этом класс аргументов будет сгенерирован в JS, а класс делегата – нет, поскольку в Microsoft AJAX Library делегаты не строго типизированные).

  • Знакомство со 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)

  • Знакомство со Script#

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

    Итак, Script# - это компилятор C#, который на выходе вместо IL выдает яваскриптовый код. Создает его Nikhil Kothari, один из архитекторов в ASP.NET Team. Кроме непосредственно самого компилятора, в проект входит несколько сборок, а также шаблоны проектов для VS. На данный момент последней является версия 0.3.

    Перечислю основные приемущества:

    ·         Строгая типизация и сопутствующий ей интеллисенс.

    ·         Строгая сингатура методов и делегатов.

    ·         Ошибки, связанные с неверными типами или сигнатурой, стали ошибками исполнения

    ·         Возможность использовать перечисления вместо строковых констант

    ·         Расширяемость. Script# позволяет создавать прокси для любых классов и глобальных функций, написанных на JavaScript

    ·         Script# содержит прокси для всех клиентских классов MS AJAX

    ·         Все возможности C# в плане рефакторинга

    ·         Удобен для создания типов, используемых в PageMethods

    и недостатки:

    ·         Требует создания отдельного проекта

    ·         Неподдерживаются некоторые возможности C# 2.0 (например, generics, partial classes)

    ·         Не для всех свойств DOM-элементов есть готовые прокси (особенно для тех, которые отсутствуют в IE)

    ·         В проектах, совместимых с ASP.NET AJAX, не поддерживается foreach и события нужно объявлять явно (используя ключевые слова add/remove)

    ·         Нет кастум-тула, который можно было бы использовать для кодогенерации прокси к PageMethods

    При всех этих минусах, лично для меня плюсы перевешывают. Кроме того, многие проблемы излечимы. Об известных мне способах «излечения» расскажу в следующий раз.

     

© 2007 Kazan Developers Community and Post`s Authors