DuoCode: транслируем C# в JavaScript

    Есть такой язык программирования, который называется C#. И есть очень много разработчиков, которым он очень нравится. А ещё есть такой язык программирования, который называется JavaScript. Как-то так сложилось, что он нравится далеко не всем C#-разработчикам. А теперь представьте ситуацию: есть заядлый C#-разработчик. Он очень любит C#, все-все проекты на нём пишет. Но судьба распорядилась так, что ему понадобилось написать клиентское веб-приложение. Знаете, такое, чтобы пользователю не нужно было себе ничего скачивать и устанавливать, чтобы он мог просто открыть любой браузер в любой операционной системе на любом устройстве — а приложение уже там. И вот тут у нашего лирического героя возникла проблема: вроде бы JavaScript идеально подходит для этой задачи, но вот писать на нём отчего-то не очень хочется. К счастью, в современном мире существует много языков, которые транслируются в JavaScript (всякие TypeScript, CoffeScript и тысячи других). Но наш разработчик оказался очень упрямым: он упорно не хочет изменять своему любимому C# с «вражескими» технологиями.

    К счастью для него, счастливое будущее уже практически наступило. Есть такой проект, который называется DuoCode. Он умеет транслировать C#-код в JavaScript. Пока он в состоянии beta, но у него уже весьма неплохо получается: поддерживаются нововведения C# 6.0, Generic-типы, Reflection, структуры и LINQ, а отлаживать итоговый JavaScript можно на исходном C#. Давайте посмотрим внимательнее, что же представляет из себя продукт.



    Hello DuoCode

    Понять происходящее проще всего на примерах. Начнём с классического *Hello world*. Итак, имеем замечательный C#-код:

    // Original C# code
    using System;
    using DuoCode.Dom;
    using static DuoCode.Dom.Global; // C# 6.0 'using static' syntax
    
    namespace HelloDuoCode
    {
      static class Program
      {
        public class Greeter
        {
          private readonly HTMLElement element;
          private readonly HTMLElement span;
          private int timerToken;
    
          public Greeter(HTMLElement el)
          {
            element = el;
            span = document.createElement("span");
            element.appendChild(span);
            Tick();
          }
    
          public void Start()
          {
            timerToken = window.setInterval((Action)Tick, 500);
          }
    
          public void Stop()
          {
            window.clearTimeout(timerToken);
          }
    
          private void Tick()
          {
            span.innerHTML = string.Format("The time is: {0}", DateTime.Now);
          }
        }
    
        static void Run()
        {
          System.Console.WriteLine("Hello DuoCode");
    
          var el = document.getElementById("content");
          var greeter = new Greeter(el);
          greeter.Start();
        }
      }
    }
    

    Лёгким движением руки он превращается в JavaScript:

    // JavaScript code generated by DuoCode
    var HelloDuoCode = this.HelloDuoCode || {};
    var $d = DuoCode.Runtime;
    HelloDuoCode.Program = $d.declare("HelloDuoCode.Program", System.Object, 0, $asm, function($t, $p) {
        $t.Run = function Program_Run() {
            System.Console.WriteLine$10("Hello DuoCode");
    
            var el = document.getElementById("content");
            var greeter = new HelloDuoCode.Program.Greeter.ctor(el);
            greeter.Start();
        };
    });
    HelloDuoCode.Program.Greeter = $d.declare("Greeter", System.Object, 0, HelloDuoCode.Program, function($t, $p) {
        $t.$ator = function() {
            this.element = null;
            this.span = null;
            this.timerToken = 0;
        };
        $t.ctor = function Greeter(el) {
            $t.$baseType.ctor.call(this);
            this.element = el;
            this.span = document.createElement("span");
            this.element.appendChild(this.span);
            this.Tick();
        };
        $t.ctor.prototype = $p;
        $p.Start = function Greeter_Start() {
            this.timerToken = window.setInterval($d.delegate(this.Tick, this), 500);
        };
        $p.Stop = function Greeter_Stop() {
            window.clearTimeout(this.timerToken);
        };
        $p.Tick = function Greeter_Tick() {
            this.span.innerHTML = String.Format("The time is: {0}", $d.array(System.Object, [System.DateTime().get_Now()])); // try to put a breakpoint here
        };
    });
    

    Выглядит это примерно так:



    Подскажу, на что стоит обратить внимание:
    • Поддерживается синтаксис using static из C# 6.0.
    • Можно легко работать с консолью, которая отображается внизу вашего приложения.
    • Можно работать с DOM-элементами
    • Работает таймер
    Даже этот простой пример уже радует. Но подобное приложение и на самом JavaScript не так сложно написать. Давайте посмотрим примеры поинтереснее.

    Крестики-нолики

    В дистрибутив входит пример написания замечательной HTML-игры, написанной на чистом C#:



    Код игры включает enum-ы и индексаторы:

    public enum Player
    {
        None = 0,
        X = 1,
        O = -1
    }
    
    public sealed class Board
    {
        public static Player Other(Player player)
        {
            return (Player)(-(int)player);
        }
    
        private readonly Player[] Squares;
    
        public readonly int Count;
    
        public Player this[int position]
        {
            get
            {
                return Squares[position];
            }
        }
    
        public Board() // empty board
        {
            //Squares = new Player[9];
            Squares = new Player[] { Player.None, Player.None, Player.None, Player.None, Player.None, Player.None, Player.None, Player.None, Player.None };
        }
    
        private Board(Board board, Player player, int position) :
          this()
        {
            Array.Copy(board.Squares, Squares, 9);
            Squares[position] = player;
    
            Count = board.Count + 1;
        }
    
        public bool Full { get { return Count == 9; } }
    
        public Board Move(Player player, int position)
        {
            if (position < 0 ||
                position >= 9 ||
                Squares[position] != Player.None)
            {
                throw new Exception("Illegal move");
            }
    
            return new Board(this, player, position);
        }
    
        public Player GetWinner()
        {
            if (Count < 5)
                return Player.None;
    
            Player result;
            bool winning =
              IsWinning(0, 1, 2, out result) ||
              IsWinning(3, 4, 5, out result) ||
              IsWinning(6, 7, 8, out result) ||
              IsWinning(0, 3, 6, out result) ||
              IsWinning(1, 4, 7, out result) ||
              IsWinning(2, 5, 8, out result) ||
              IsWinning(0, 4, 8, out result) ||
              IsWinning(2, 4, 6, out result);
    
            return result;
        }
    
        private bool IsWinning(int p0, int p1, int p2, out Player player)
        {
            int count = (int)Squares[p0] + (int)Squares[p1] + (int)Squares[p2];
            player = count == 3 ? Player.X : count == -3 ? Player.O : Player.None;
            return player != Player.None;
        }
    }
    

    Обратите внимание, как ловно удаётся управляться с DOM-элементами:

    public static void Main(string[] args)
    {
      for (var i = 0; i < 9; i++)
      {
        Dom.HTMLInputElement checkbox = GetCheckbox(i);
        checkbox.checked_ = false;
        checkbox.indeterminate = true;
        checkbox.disabled = false;
        checkbox.onclick = OnClick;
      }
    
      if (new Random().Next(2) == 0)
        ComputerPlay();
    
      UpdateStatus();
    }
    
    private static dynamic OnClick(Dom.MouseEvent e)
    {
      int position = int.Parse(((Dom.HTMLInputElement)e.target).id[1].ToString());
    
      try
      {
        board = board.Move(Player.X, position);
      }
      catch
      {
        Dom.Global.window.alert("Illegal move");
        return null;
      }
    
      Dom.HTMLInputElement checkbox = GetCheckbox(position);
      checkbox.disabled = true;
      checkbox.checked_ = true;
    
      if (!board.Full)
        ComputerPlay();
    
      UpdateStatus();
    
      return null;
    }
    
    private static Dom.HTMLInputElement GetCheckbox(int index)
    {
      string name = "a" + index.ToString();
      Dom.HTMLInputElement checkbox = Dom.Global.document.getElementById(name).As<Dom.HTMLInputElement>();
      return checkbox;
    }
    

    WebGL

    Хотите работать с WebGL? Нет проблем! Берём C#-код:

    using DuoCode.Dom;
    using System;
    
    namespace WebGL
    {
      using GL = WebGLRenderingContext;
    
      internal static class Utils
      {
        public static WebGLRenderingContext CreateWebGL(HTMLCanvasElement canvas)
        {
          WebGLRenderingContext result = null;
          string[] names = { "webgl", "experimental-webgl", "webkit-3d", "moz-webgl" };
          foreach (string name in names)
          {
            try
            {
              result = canvas.getContext(name);
            }
            catch { }
            if (result != null)
              break;
          }
          return result;
        }
    
        public static WebGLShader CreateShaderFromScriptElement(WebGLRenderingContext gl, string scriptId)
        {
          var shaderScript = (HTMLScriptElement)Global.document.getElementById(scriptId);
    
          if (shaderScript == null)
            throw new Exception("unknown script element " + scriptId);
    
          string shaderSource = shaderScript.text;
    
          // Now figure out what type of shader script we have, based on its MIME type
          int shaderType = (shaderScript.type == "x-shader/x-fragment") ? GL.FRAGMENT_SHADER :
                           (shaderScript.type == "x-shader/x-vertex")   ? GL.VERTEX_SHADER   : 0;
          if (shaderType == 0)
            throw new Exception("unknown shader type");
    
          WebGLShader shader = gl.createShader(shaderType);
          gl.shaderSource(shader, shaderSource);
    
          // Compile the shader program
          gl.compileShader(shader);
    
          // See if it compiled successfully
          if (!gl.getShaderParameter(shader, GL.COMPILE_STATUS))
          {
            // Something went wrong during compilation; get the error
            var errorInfo = gl.getShaderInfoLog(shader);
            gl.deleteShader(shader);
            throw new Exception("error compiling shader '" + shader + "': " + errorInfo);
          }
          return shader;
        }
    
        public static WebGLProgram CreateShaderProgram(WebGLRenderingContext gl, WebGLShader fragmentShader, WebGLShader vertexShader)
        {
          var shaderProgram = gl.createProgram();
          gl.attachShader(shaderProgram, vertexShader);
          gl.attachShader(shaderProgram, fragmentShader);
          gl.linkProgram(shaderProgram);
    
          bool linkStatus = gl.getProgramParameter(shaderProgram, GL.LINK_STATUS);
          if (!linkStatus)
            throw new Exception("failed to link shader");
          return shaderProgram;
        }
    
        public static WebGLTexture LoadTexture(WebGLRenderingContext gl, string resourceName)
        {
          var result = gl.createTexture();
          var imageElement = Properties.Resources.duocode.Image;
          imageElement.onload = new Func<Event, dynamic>((e) =>
          {
            UploadTexture(gl, result, imageElement);
            return true;
          });
    
          return result;
        }
    
        public static void UploadTexture(WebGLRenderingContext gl, WebGLTexture texture, HTMLImageElement imageElement)
        {
          gl.pixelStorei(GL.UNPACK_FLIP_Y_WEBGL, GL.ONE);
          gl.bindTexture(GL.TEXTURE_2D, texture);
          gl.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, GL.RGBA, GL.UNSIGNED_BYTE, imageElement);
          gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.LINEAR);
          gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.LINEAR_MIPMAP_NEAREST);
          gl.generateMipmap(GL.TEXTURE_2D);
          gl.bindTexture(GL.TEXTURE_2D, null);
        }
    
        public static float DegToRad(float degrees)
        {
          return (float)(degrees * System.Math.PI / 180);
        }
      }
    }
    

    И применяем к нему DuoCode-магию:

    WebGL.Utils = $d.declare("WebGL.Utils", System.Object, 0, $asm, function($t, $p) {
        $t.CreateWebGL = function Utils_CreateWebGL(canvas) {
            var result = null;
            var names = $d.array(String, ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"]);
            for (var $i = 0, $length = names.length; $i != $length; $i++) {
                var name = names[$i];
                try {
                    result = canvas.getContext(name);
                }
                catch ($e) {}
    
                if (result != null)
                    break;
            }
            return result;
        };
        $t.CreateShaderFromScriptElement = function Utils_CreateShaderFromScriptElement(gl, scriptId) {
            var shaderScript = $d.cast(document.getElementById(scriptId), HTMLScriptElement);
    
            if (shaderScript == null)
                throw new System.Exception.ctor$1("unknown script element " + scriptId);
    
            var shaderSource = shaderScript.text;
    
            // Now figure out what type of shader script we have, based on its MIME type
            var shaderType = (shaderScript.type == "x-shader/x-fragment") ? 35632 /* WebGLRenderingContext.FRAGMENT_SHADER */ : (shaderScript.type == "x-shader/x-vertex") ? 35633 /* WebGLRenderingContext.VERTEX_SHADER */ : 0;
            if (shaderType == 0)
                throw new System.Exception.ctor$1("unknown shader type");
    
            var shader = gl.createShader(shaderType);
            gl.shaderSource(shader, shaderSource);
    
            // Compile the shader program
            gl.compileShader(shader);
    
            // See if it compiled successfully
            if (!gl.getShaderParameter(shader, 35713 /* WebGLRenderingContext.COMPILE_STATUS */)) {
                // Something went wrong during compilation; get the error
                var errorInfo = gl.getShaderInfoLog(shader);
                gl.deleteShader(shader);
                throw new System.Exception.ctor$1("error compiling shader '" + $d.toString(shader) + "': " + errorInfo);
            }
            return shader;
        };
        $t.CreateShaderProgram = function Utils_CreateShaderProgram(gl, fragmentShader, vertexShader) {
            var shaderProgram = gl.createProgram();
            gl.attachShader(shaderProgram, vertexShader);
            gl.attachShader(shaderProgram, fragmentShader);
            gl.linkProgram(shaderProgram);
    
            var linkStatus = gl.getProgramParameter(shaderProgram, 35714 /* WebGLRenderingContext.LINK_STATUS */);
            if (!linkStatus)
                throw new System.Exception.ctor$1("failed to link shader");
            return shaderProgram;
        };
        $t.LoadTexture = function Utils_LoadTexture(gl, resourceName) {
            var result = gl.createTexture();
            var imageElement = WebGL.Properties.Resources().get_duocode().Image;
            imageElement.onload = $d.delegate(function(e) {
                WebGL.Utils.UploadTexture(gl, result, imageElement);
                return true;
            }, this);
    
            return result;
        };
        $t.UploadTexture = function Utils_UploadTexture(gl, texture, imageElement) {
            gl.pixelStorei(37440 /* WebGLRenderingContext.UNPACK_FLIP_Y_WEBGL */, 1 /* WebGLRenderingContext.ONE */);
            gl.bindTexture(3553 /* WebGLRenderingContext.TEXTURE_2D */, texture);
            gl.texImage2D(3553 /* WebGLRenderingContext.TEXTURE_2D */, 0, 6408 /* WebGLRenderingContext.RGBA */, 6408 /* WebGLRenderingContext.RGBA */, 5121 /* WebGLRenderingContext.UNSIGNED_BYTE */, imageElement);
            gl.texParameteri(3553 /* WebGLRenderingContext.TEXTURE_2D */, 10240 /* WebGLRenderingContext.TEXTURE_MAG_FILTER */, 9729 /* WebGLRenderingContext.LINEAR */);
            gl.texParameteri(3553 /* WebGLRenderingContext.TEXTURE_2D */, 10241 /* WebGLRenderingContext.TEXTURE_MIN_FILTER */, 9985 /* WebGLRenderingContext.LINEAR_MIPMAP_NEAREST */);
            gl.generateMipmap(3553 /* WebGLRenderingContext.TEXTURE_2D */);
            gl.bindTexture(3553 /* WebGLRenderingContext.TEXTURE_2D */, null);
        };
        $t.DegToRad = function Utils_DegToRad(degrees) {
            return (degrees * 3.14159265358979 /* Math.PI */ / 180);
        };
    });
    

    Вы можете самостоятельно потыкать демку на официальном сайте. Выглядит это примерно так:



    RayTracer

    И это не предел! Один из примеров включает полноценный RayTracer (с векторной математикой, работой с цветом и освещением, камерой и поверхностями — всё на чистом C#):



    Отладка

    Звучит невероятно, но отлаживать это чудо можно прямо в браузере. C#-исходники прилагаются:



    На текущий момент отладка возможна в VS 2015, IE, Chrome и Firefox.

    Ещё пара примеров

    При трансляции из C# в JavaScript одним из самых больных вопросов являются структуры. Сегодня DuoCode поддерживает только неизменяемые структуры, но для хорошего проекта этого должно хватить (как мы знаем, мутабельных структур следует избегать).

    C#:

    public struct Point
    {
        public readonly static Point Zero = new Point(0, 0);
    
        public readonly int X;
        public readonly int Y;
    
        public Point(int x, int y)
        {
            X = x;
            Y = y;
        }
    }
    

    JavaScript:

    HelloDuoCode.Program.Point = $d.declare("Point", null, 62, HelloDuoCode.Program, function($t, $p) {
        $t.cctor = function() {
            $t.Zero = new HelloDuoCode.Program.Point.ctor$1(0, 0);
        };
        $t.ctor = function Point() {
            this.X = 0;
            this.Y = 0;
        };
        $t.ctor.prototype = $p;
        $t.ctor$1 = function Point(x, y) {
            this.X = x;
            this.Y = y;
        };
        $t.ctor$1.prototype = $p;
    });
    

    Лично меня особенно радует, что есть полноценная поддержка LINQ:

    C#:

    public static IEnumerable<int> Foo()
    {
        return Enumerable.Range(0, 10).Where(x => x % 2 == 0).Select(x => x * 3);
    }
    

    JavaScript:

    $t.Foo = function Program_Foo() {
        return System.Linq.Enumerable.Select(System.Int32, System.Int32, System.Linq.Enumerable.Where(System.Int32, 
            System.Linq.Enumerable.Range(0, 10), $d.delegate(function(x) {
                return x % 2 == 0;
            }, this)), $d.delegate(function(x) {
            return x * 3;
        }, this));
    };
    

    Мелкие радости вроде Generic, params, nullable, перегрузка методов, значения по умолчанию также идут в комплекте:

    C#:

    public class Foo<T> where T : IComparable<T>
    {
        public void Bar(int? x, T y, string z = "value")
        {
            System.Console.WriteLine((x ?? -1) + y.ToString() + z);
        }
        public void Bar(string z, params object[] args)
        {
        }
    }
    // Main
    new Foo<int>().Bar(null, 2);
    

    JavaScript:

    HelloDuoCode.Program.Foo$1 = $d.declare("Foo`1", System.Object, 256, HelloDuoCode.Program, function($t, $p, T) {
        $t.ctor = function Foo$1() {
            $t.$baseType.ctor.call(this);
        };
        $t.ctor.prototype = $p;
        $p.Bar$1 = function Foo$1_Bar(x, y, z) {
            System.Console.WriteLine$10($d.toString(($d.ncl(x, -1))) + y.ToString() + z);
        };
        $p.Bar = function Foo$1_Bar(z, args) {};
    }, [$d.declareTP("T")]);
    // Main
    new (HelloDuoCode.Program.Foo$1(System.Int32).ctor)().Bar$1(null, 2, "value");
    

    Заключение

    Напомню, что DuoCode пока находится в состоянии beta, но уже на сегодняшний день список фич приятно радует глаз:



    Разработка идёт достаточно быстро, постоянно выходят обновления с новыми возможностями. Будем надеяться, что мы уже буквально в паре шагов от того светлого будущего, когда можно будет писать действительно сложные клиентские веб-приложения на C#, используя всю мощь языка и сопутствующих инструментов разработки.
    Enterra 43,62
    Компания
    Поделиться публикацией
    Похожие публикации
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 54
      +1
      Простейшие примеры выглядят как-то неоправданно раздуто. На чистом C# и на чистом JS было бы гораздо компактнее.

        +1
        на чистом JS было бы гораздо компактнее

        Ну, с этим никто не спорит, у подобного подхода есть своя цена. «Раздутость» кода связана с поддержкой всех C#-штук, которые мы должны уметь делать. К счастью, сгенерированный JavaScript нам не нужен для дальнейшей работы — разрабатывать и отлаживать можно на C#.
          +1
          А что делать, когда есть большой кусок кода / библиотека написанная на JS. Как реализовано взаимодействие C#<->JS?

          Можно ли из C# дернуть JS методы, а из JS потом вызвать C#-колбек?
            0
            Не уверен, не изучал вопрос. Попробуйте написать авторам библиотеки.
              0
              Через dynamic поди
                0
                Если вас интересует серверный JS, то есть библиотека edge, которая позволяет делать интероп между кодом на C# и JS без всякой трансляции.

                Если же говорить именно про Duocode, то тут всё странно. В официальном FAQ есть только про вызовы C# из JS, а для всего остального они вроде бы написали врапперы. Всегда можно, конечно, посмотреть, как они делают эти врапперы, но есть более прямолинейный подход. Вы можете в своём коде при инициализации получить объект со списком лямбда-функций. Когда JS вызывает ваш код на C#, он передаёт в него ссылки на все необходимые функции, и вы их потом вызываете.

                Но я бы не стал использовать Duocode, пока не появится серьёзных портов чего-нибудь с C# в браузер. Все существующие трансляторы (Netjs, IL2JS) так и не были доведены до конца вследствие технических сложностей такой реализации.
            +5
            «А теперь представьте ситуацию: есть заядлый C#-разработчик. Он очень любит C#, все-все проекты на нём пишет. Но судьба распорядилась так, что ему понадобилось написать клиентское веб-приложение.»

            И он берёт TypeScript и радуется жизни. Или не берёт, но почему?
              0
              TypeScript — отличная штука. Но предыдущее детище Хейлсберга всё-таки радует намного больше в плане синтаксиса и инструментария.
                +2
                (Disclaimer: мне нравится C#, как язык.)

                Потому что заядлые C#-разработчики такие заядлые, видимо. Многие просто не хотят ничего учить.
                  +1
                  Тут не в обучении дело, TypeScript достаточно простой. Но C# всё-равно значительно обгоняет его в плане возможностей.
                    +1
                    JS-то еще проще.
                    значительно обгоняет его в плане возможностей

                    Я в принципе понимаю, что вы хотите сказать, но вообще-то JS обладает полнотой по Тьюрингу:) Единственное, чего мне действительно не хватает — это атрибутов.
                      +1
                      Ну, это не аргумент, Brainfuck тоже полный по тьюрингу. В C# есть мощный reflection, богатые возможности по работе с ООП, клёвый LINQ, куча синтаксического сахара (особенно с C# 6). Да, я знаю, что в JS есть куча библиотек, которые всё это пытаются эмулировать, но функционал всё равно не тот.
                      Плюс, не стоит забывать про инструментарий. Да, статическая типизация в TypeScript помогает, но с помощью VS2015+ReSharper9 я могу за 5 минут выполнить такой рефакторинг большого проекта, на который в любом другом трансляторе в JS у меня бы ушёл минимум день. Да, JS проще, маленькие проекты на нём быстро поднимаются. Но если у вас сотни тысяч строк бизнес-логики, то поддерживать такой проект на JavaScript — не самая лёгкая задача.
                        +4
                        Ну, это не аргумент. Вместо LINQ у нас map/reduce и карринг, ООП у нас тоже есть, сахар — дело вкуса. Reflection'а не хватает, тут согласен. Рефакторинг я делаю в WebStorm точно с такой же скоростью.

                        Но если у вас сотни тысяч строк бизнес-логики, то поддерживать такой проект на JavaScript — не самая лёгкая задача.

                        Я что-то сомневаюсь в больших проектах на компилируемых в JS языках. Во-первых, из-за поддержки всех тех фич все это будет неимоверно весить и тормозить, во-вторых, рано или поздно придется дебажить в каком-нибудь IE9, в котором поддержки source map нету и не будет. В-третьих, этого GWT-подобного добра сколько угодно, и ни один большой проект на них не написан, почему-то.

                        Набросать что-то на скорую руку, если JS ты не знаешь, а надо срочно — запросто. А делать что-то большое, не зная досконально нижележащих технологий — рискованно.
                          0
                          Ок, а такой сценарий: у меня уже есть C#-проект, я хочу портировать его в веб. Silverlight не оправдал ожиданий, а других альтернатив-то и нету.
                            +2
                            В таком сценарии возражений не имею.
                        • НЛО прилетело и опубликовало эту надпись здесь
                            0
                            Ок, Catberry намекает на то, что если руки растут из того места, то и правда можно. Увы, таких разработчиков не так уж и много. А C# просто не даст совершить тех ошибок, которые среднестатистический Js-разработчик делает каждый день (это не камень в сторону Js, просто говнокодеров в последнее время слишком много развелось). Плюс, повторю мысли из соседних комментариев о пользе DuoCode-подхода:
                            • Более богатый синтаксис и возможности платформы (тот же reflection). Ну не может тут Js составить достойную конкуренцию, не его это судьба.
                            • Портирование существующих C#-проектов на сторону клиента

                            А по поводу больших проектов: как бы не развивался Js в наши дни, я не верю, что возможности рефакторинга сравнятся с C#+VS2015+R#. Плюс, если проект хорошо написан, то большая часть проблем уходит сразу. А если он плохо написан? Мне доводилось делать рефакторинг огромного количества говнокода на C#, инструментарий сделал эту работу не такой сложной. А если бы это был проект с нестрогой динамической типизацией, то я бы скорее плюнул и написал бы с нуля и нормально.
                            • НЛО прилетело и опубликовало эту надпись здесь
                                0
                                А reflection лично мне за всю разработку на JS особо не понадобился.

                                Это больше вопрос привычек и парадигмы мышления. Когда много и хорошо решаешь какие-то задачи с помощью какого-то инструмента, то сразу замечаешь, когда инструмент у тебя отобрали. Например, я как функциональщиной увлёкся, начал иногда в C# ощущать небольшой дискомфорт (LINQ вывозит, но не всегда).
                                Как по мне — основные преимущества динамических языков проявляются в небольших проектах. Например, если мне нужно что-то небольшое написать, то я порой с C# переключаюсь на Python. А в большом проекте с динамическими фишками нужно быть аккуратным. Если нет достаточно опыта и квалификации, то можно крайне легко усложнить себе дальнейшую жизнь.
                                0
                                это не камень в сторону Js, просто говнокодеров в последнее время слишком много развелось


                                Я полгода как ушел с большого проекта на шарпе. Если человеку важна только скорость, а не качество, если он не знает основ, если он, в конце концов, кое-какер — то его не спасет то, что он шарпер.
                    +1
                    Чем это отличается от Script#?
                      0
                      Script# (он всё ещё живой?) и другие подобные поделки имеют очень ограниченный функционал, ибо распарсить C# самим и выполнить нормальную трасляцию всех фич (а потом ещё и дорабатывать продукт под новые версии C#) — крайне сложная задача. DuoCode использует Roslyn для получения синтаксического дерева, что убивает основную сложность. Больше нет ограничений и багов по работе с самим C# — остаётся только аккуратно сгенерировать JavaScript по синтаксическому дереву.
                        0
                        Он уже мерт, да и в нем нет поддержки даже .NET 4.0, не говоря уже о более современных версиях.
                        +2
                        SharpLang — на входе откомпиленые в MSIL сборки, на выходе LLVM-представление, которое можно скармливать emscripten. Почему-то есть мнение, что оно со временем будет по поддержке фич и совместимости ощутимо лучше любых средств, занимающихся перегоном шарпового AST в JS.
                          0
                          1. Проект хороший, но пока немного сыроватый. Очень надеюсь, что в скором времени ребята допилят его до хорошего уровня.
                          2. MSIL->LLVM — это прикольно, но если начинать работать сразу с IL, то не будем ли мы лишены полезной для трансляции информации? Выхлоп DuoCode местами пока немного пугает, но он вполне читаем: легко можно разобраться что и где делается. Не думаю, что из IL (а особенно после трансляции в LLVM) можно вытащить код такого же уровня читаемости.
                            +1
                            По идее к MSIL прилагается pdb с нужной инфой, а дальше уже включаются штатные средства LLVM для поддержки работы с отладочной информацией. То есть в итоге мы таки получим .map-файлы нужные отладчику.
                              0
                              Ну, хорошо, если так. Будем следить за развитием проекта.
                              0
                              Трансляция LLVM в asm.js не предполагает читаемости, поскольку asm.js сделан не для людей. У таких модулей всегда есть исходный код (в данном случае C#), и есть чёрный ящик на JS с известным нам API.

                              Мне кажется, вам просто более привычен подход TypeScript «давайте сделаем выдачу читаемой». Я как раз никогда не мог понять, зачем это нужно. Вы не могли бы объяснить?
                                0
                                Зачем делать результат работы typescript читаемым? Чтобы скомпилированный код можно было нормально отладить когда map файл не доступен или не позволяет понять суть происходящего.
                                  –1
                                  Хм, вопросов стало только больше.

                                  Если source map не доступен, нужно сделать его доступным. У разработчика есть возможность, у остальных нет необходимости.

                                  Если транслятор статически типизированного языка не даёт гарантий относительно качества target кода, то транслятору место на свалке истории. Даже в C/С++ с их чудесами при наличии undefined behaviour в коде никогда не нужно отлаживать ассемблер. Если такие ситуации возникают, то вместо отладки транслированного кода нужно создавать тикеты вот здесь.
                                    –1
                                    Source map не доступен в IE, например, и тут от разработчика ничего не зависит.
                                      –1
                                      Да доступен уже почти год как.

                                      Если говорить про более старые версии IE, то необходимость отлаживать исходный код сугубо из-под IE может быть связана только с тем, что не использованы fallback/shim библиотеки для работы с некоторым функционалом с плохой кросс-браузерностью. Разработчиков таких библиотек, конечно, уже ничего не спасёт, но я пока не слышал ни об одной библиотеке в духе jQuery или es5-shim, написанной на TypeScript.

                                      К сожалению, от разработчика всегда всё зависит.
                                        –1
                                        Странно, еще в декабре соурс-мапы не сработали у меня. В любом случае, старые IE еще какое-то время никуда не денутся.
                                        fallback/shim существует не для всего, и то, что есть, может быть с багами, которые нужно отловить.
                            0
                            Есть такой вопрос, зачем все это? Ведь даже микрософт не сделал такого транслятора, а создал TypeScript.
                              0
                              TypeScript появился в 2012, когда Roslyn находился в весьма зачаточном состоянии, а вести удобную разработку Js-прилоежний уже хотелось.
                              Вопрос про политику Microsoft заслуживает отдельного обсуждения. Я думаю так: TypeScript — универсальная штука, которую можно использовать для любых Js-проектов. А DuoCode — нишевое решение, оно подойдёт не всем (зато в своей нише может очень хорошо пригодится).
                                +1
                                Хочется странного. Вон, Nemerl'овцы всё туда же.
                                +5
                                Выбрать в качестве разработки язык C#, чтобы потом внутри писать document.getElementById… Это какое-то извращение. C# прекрасный язык, но имхо инструмент нужно выбирать под задачу.
                                  0
                                  А с другой стороны хочется удобный инструмент. Плюс, отписывался выше: что если нужно портировать существующую C#-логику на сторону клиента?
                                    +1
                                    У нас сейчас как раз проект, подразумевающий переписывание клиента с Silverlight на HTML5. Автоматическая конвертация, как впрочем и ручное построчное переписывание, превращает приложение в "лет ми спик фром май харт". Так что в долгосрочной перспективе, если подразумевается хоть какое-нибудь развитие и поддержка, лучше использовать «родные» вебовские подходы.
                                      0
                                      У нас одна из редакций Grapholite на Silverlight написана. Огромное количество C#-кода, в котором возможности рефлексии и биндинг-магии используются на полную. Сколько не смотрели в сторону HTML5, не хватает духу попробовать подобный функционал повторить на HTML5.
                                  0
                                  В C# cтатическая типизация, всё же.
                                    0
                                    C# в этом плане, к счастью, не уникален :)
                                      –1
                                      Я бы даже сказал, что он в этом плане довольно плох, и я бы предпочёл куда более строго типизированный PureScript. Но это уже намного лучше, чем типизация JS с её сплошным WAT.
                                        0
                                        Что значит, «более строго типизирован»? С# действительно где-то делает недостаточно строгие проверки, или вы имеете в виду, что хотелось бы больше гибкости, вроде зависимых типов и т.д.?
                                          0
                                          Зависимых типов хотелось бы, конечно. Настоящих, не path-dependent.
                                  0
                                  Интересно, можно ли будет скомпилить сам Roslyn под JavaScript? Давно уже есть одна идея, может как раз получится ее реализовать.
                                    0
                                    Идея крайне увлекательная, но сложная. Если получится, то будет мега-круто.
                                      0
                                      Благодаря существованию трансляторов CIL -> JS скомпилить можно. А вот насколько оно будет юзабельно…
                                        0
                                        Пытался это сделать с помощью jsil.org, сыпались ошибки. Но тогда не стал особо заморачиваться.
                                        0
                                        Не думаю, что сейчас есть хоть один транслятор, способный на это. У всех существующих есть ошибка в реализации разных моментов языка, от встроенных массивов размерности больше 1 до полного отсутствия атрибутов и рефлексии. Крупные проекты обычно хоть что-нибудь из этого используют, а поэтому не транслируются.
                                        0
                                        Раньше занимался задачей написания универсального C# кода под .NET и JavaScript. В итоге удалось создать общую графическую библиотеку, использующую функции GDI+ под .NET и функции canvas из html5 под JavaScript. Реализовывал тогда это с помощью Script#. Думаю, с помощью данной либы получилось бы реализовать все более изящно, без многочисленного количества костылей.
                                          0
                                          Попробовал установить, но оказалось, что данные проект пока что находится в закрытой бете. Будем ждать…
                                            0
                                            Нашел недавно WootzJs, тоже построенный на Roslyn. Ну и еще есть трансляторы, построенные на NRefactory (SaltarelleCompiler). Интересно, зачем их так много?

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

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