DuoCode:JavaScriptでのC#の翻訳

C#と呼ばれ​​るプログラミング言語があります。 そして、それを本当に気に入っている開発者がたくさんいます。 また、JavaScriptと呼ばれるプログラミング言語もあります。 どういうわけか、すべてのC#開発者がそれを好むわけではありません。 ここで状況を想像してください。熱心なC#開発者がいます。 彼はC#が大好きで、すべてのプロジェクトを書いています。 しかし、運命は、彼がクライアントWebアプリケーションを書く必要があることを決定しました。 ユーザーが何かをダウンロードしてインストールする必要がないので、ユーザーはデバイスのオペレーティングシステムでブラウザーを簡単に開くことができ、アプリケーションは既にそこにあります。 そして、ここで私たちの叙情的なヒーローに問題がありました:JavaScriptはこのタスクに理想的であるように見えますが、何らかの理由で私はそれについて本当に書きたくありません。 幸いなことに、現代の世界には、JavaScriptに翻訳される多くの言語があります(あらゆる種類のTypeScriptCoffeScript 、その他数千)。 しかし、開発者は非常に頑固であることが判明しました。彼は頑固に「敵」のテクノロジーで愛するC#をだまそうとはしません。



彼にとって幸いなことに、幸せな未来がほとんど来ています。 DuoCodeと呼ばれるプロジェクトがあります。 彼はJavaScriptでC#コードを翻訳することができます。 ベータ状態ではありますが、すでに十分に機能しています:C#6.0の革新、ジェネリック型、リフレクション、構造、およびLINQがサポートされており、元のC#で結果のJavaScriptをデバッグできます。 製品が何であるかを詳しく見てみましょう。







こんにちはデュオコード

何が起こっているのかを理解する最も簡単な方法は、例を通してです。 古典的な* 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 }; });
      
      





次のようになります。







次の点に注意してください。 この単純な例でも、すでに満足しています。 ただし、JavaScript自体にある同様のアプリケーションを書くのはそれほど難しくありません。 さらに興味深い例を見てみましょう。



チックタックトー

配布には、純粋なC#で記述された優れたHTMLゲームを作成する例が含まれています。







ゲームコードには、 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); }; });
      
      





公式Webサイトでデモを自分で突くことができます。 次のようになります。







レイトレーサー

そして、これは制限ではありません! 1つの例には、本格的なRayTracer(ベクトル演算、色と照明、カメラとサーフェスの操作-すべて純粋なC#)が含まれます。







デバッグ

信じられないほど聞こえますが、ブラウザでこの奇跡を直接デバッグできます。 C#ソースが含まれています:







現在、VS 2015、IE、Chrome、Firefoxでデバッグが可能です。



さらにいくつかの例

C#からJavaScriptに変換するとき、構造は最も苦痛な問題の1つです。 現在、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はまだベータ状態ですが、今日の機能のリストは目を楽しませてくれます。







開発は十分に速く、常に新しい機能で更新されます。 言語と関連する開発ツールのすべての能力を使用して、本当に複雑なクライアントサイドWebアプリケーションをC#で作成できるようになるという、明るい未来から文字通りわずか数歩離れることを期待しましょう。



All Articles