Наследуем тип .NET от JavaScript объекта с перегрузками и приватными методами

    Да, именно так и никаких уловок. Эта идея мою голову посетила около двух месяцев назад в процессе обдумывания статьи об Алгоритмах и решениях. Типы .NET в том движке использовать легко, а можно ли наоборот…

    Немного о движке
    В предыдущей статье я старался меньше писать об особенностях и больше о том, что может быть полезно в отрыве от cкриптования вообще, дабы статья была меньше похожа на саморекламу. Буквально, с первых комментариев я понял, что это было ошибкой.
    Скорость
    Он быстр. Очень быстр. За прошедший месяц с небольшим было перелопачено много кода и разобрано множество частных случаев и сделаны выводы. К примеру, в зависимости от того, как написана функция и какие языковые средства используются, её вызов может происходить по одному из более чем 10 сценариев от эквивалента инлайна до честного выделения памяти под каждую переменную и аргумент и полную инициализацию контекста.
    Простота интеграции
    Вся тяжелая «кухня» по обеспечению доступа к типам платформы скрыта за одной скромной функцией
    JSObject TypeProxy.Proxy(object)

    Вы просто отдаёте практически любой объект и результат назначаете переменной
    var script = new Script(" megaObject.alert('Hello from javascript') ");
    script.Context.DefineVariable("megaObject")
                  .Assign(TypeProxy.Proxy(new { alert = new Action<string>(x => MessageBox.Show(x)) });
    script.Invoke();
    

    Для типов, реализующих интерфейс IList есть специальная обёртка NiL.JS.Core.TypeProxing.NativeList, маскирующая такой объект под родной массив js.
    Можно зарегистрировать конструктор типа и создавать объекты уже во время выполнения сценария. Добавится переменная с именем типа.
    script.Context.AttachModule(typeof(System.Windows.Forms.Form));

    Если лень добавлять типы по одному, можно добавить целое пространство имён
    Context.GlobalContext.DefineVariable
                                ("forms") // имя переменной, через которую будет доступно пространство имён.
                                .Assign(new NamespaceProvider
                                    ("System.Windows.Forms")); // пространство имён, к которому будет осуществляться доступ.

    Не только выполнение
    Каждый узел синтаксического дерева доступен извне сборки. Вы можете реализовать свою виртуальную машину, транслятор в другой язык, статический анализатор (если будет недостаточно интегрированного) или ещё что-то, на что способна ваша фантазия. Каждое использование всех переменных хранит ссылку на соответствующий дескриптор, который может рассказать некоторую информацию о ней. Например, показать все места использования, которые «выжили» после оптимизации. А пару недель назад был реализован, так называемый, Visitor с помощью которого всё перечисленное сделать ещё проще.

    Общая схема


    Для того, чтобы наследовать тип в платформе .NET нужна сборка, лежащая на диске, в которой хранится информация о типе (метаданные). Но пока JS файл лежит на диске, там никаких типов нет. Они там появятся только во время выполнения, когда логика этого сценария разделит функции на, собственно, функции и конструкторы. Решение этой загвоздки нашлось почти сразу — я добавил в глобальный контекст функцию, которая принимает на вход конструктор («registerClass»). Таким образом, я, как бы, прошу мне показать, для каких js-функций стоит генерировать метаданные. Однако, это требует холостого запуска.
    После того, как выполнение закончилось, с помощью System.Reflection.Emit создаётся сохраняемая сборка в которой объявляется по одному классу для каждого зарегистрированного конструктора плюс ещё один статический, который будет хранить код javascript и запускать его при первом обращении. На этом этапе запуск registerClass() уже используется для того, чтобы поставить в соответствие типы в сборке с типами в сценарии. Все типы-обёртки унаследованы от одного типа в котором описаны базовые механизмы взаимодействия. Состав типов формируется на основе того, что было найдено в прототипе конструктора.
    function ctor() {
    }
    ctor.prototype // есть у каждой функции кроме некоторых встроенных
    

    Таким образом, если добавить неперечисляемое свойство в прототип, оно не попадёт в метаданные и станет «приватным».

    Отлично, теперь можно создавать объекты JS как объекты .NET, но где обещанные наследование и перегрузки?!

    Это решилось проще. Для того, чтобы понять, какие методы перегружены, в конструкторе базового типа выполняется проход по всем функциям в типе (понимаю, не совсем удачное место, но для прототипа реализации этого достаточно) и проверяется предок типа их объявившего. Все переопределённые методы становятся объявленными в типе-наследнике. Все методы, которые найдутся такой проверкой, добавляются в JS реализацию типа. Всё.
    Теперь даже новые добавленные функции будут доступны в js, им не требуется быть перегруженными.

    Код получился не большим, вы можете посмотреть его на GitHub

    P.S. Это решение, по крайней мере, на данном этапе, не пригодно для серьёзного использования. Это эксперимент.
    Share post

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 2

      +1
      Теперь даже новые добавленные функции будут доступны в js, им не требуется быть перегруженными.

      А для чего это может понадобиться? Просто похоже на какое-то извращение, расширять javascript через .net, чтобы использовать его через javascript.
        0
        Тут не совсем расширение javascript. Это, скорее, добавление javascript в зверинец языков .NET и реализация одной из ключевых идей платформы. Это как если бы базовый класс был написан на C#, наследован в C++/CLI и использован в F#. Вот эта штука позволяет базовый класс реализовать на JavaScript.
        А указанная цитата о том, что будет работать механизм позднего связывания, родной для JS.

      Only users with full accounts can post comments. Log in, please.