Наследуем тип .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. Это решение, по крайней мере, на данном этапе, не пригодно для серьёзного использования. Это эксперимент.

    Комментарии 2

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

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

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое