A LINQ alapja a keretrendszerben a kifejezés fa (expression tree). Minden LINQ műveletsorozat egy kifejezés fát fog létrehozni. Ebből a fából a .NET keretrendszer dinamikusan generál kódot, amit aztán futtatni fog. Ez alapján készült el a DLR, ami a Dynamic Language Runtime rövidítése. Ez a CLR dinamikus implementációja, ami szkript nyelvek használatát teszi lehetővé a .NET keretrendszerben. A DLR a .NET 4.0 óta része a keretrendszernek.
Számos DLR kompatibilis nyelv létezik, amit felhasználhatunk a programunkban dinamikus kifejezés kiértékelésre. A könyv ezen fejezetében szereplő nyelvekről csak érintőlegesen lesz szó, mivel mindegyikről külön könyvet lehetne írni. A fókusz minden esetben a nyelvek DLR rendszeren keresztüli használatán lesz.
Csharp
A C# alapvetően nem szkriptnyelvnek készült, de valamikor már 2010 környékén a Mono rendelkezett interaktívan alkalmazható C# implementációval, ami lehetővé tette a nyelv szkript használatát. A Unity játékmotor erősen épít a C# szkript használatára. A Roslyn fordító megjelenése óta a szkript működése igencsak ki lett terjesztve.
A C# szkript beágyazásához a Microsoft.CodeAnalysis.CSharp.Scripting NuGet csomagot kell telepítenünk. A csomag telepítése után használható is. A csomag fő osztálya a CSharpScript és a ScriptState osztály. Az utóbbi a futtatókörnyezet jelenlegi és korábbi állapotáról tárol információkat, míg a CSharpScript osztály a kód végrehajtásáért felel. A végrehajtás aszinkron módon történik.
A következő példa a C# interactive beágyazását mutatja be a programunkba. A kódban a ScriptEngine egy egyszerű állapotgépet1 valósít meg a C# kódok végrehajtására.
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
using System;
namespace DLRCSharp
{
public class ScriptEngine
{
private static ScriptState<object> _scriptState;
public static object Execute(string code)
{
if (_scriptState == null)
_scriptState = CSharpScript.RunAsync(code).Result;
else
_scriptState = _scriptState.ContinueWithAsync(code).Result;
if (_scriptState.ReturnValue != null && !string.IsNullOrEmpty(_scriptState.ReturnValue.ToString()))
return _scriptState.ReturnValue;
return null;
}
}
class Program
{
static void Main(string[] args)
{
ScriptEngine.Execute(
@"public class Demo
{
public string HelloWorld {get;set;}
public Demo()
{
HelloWorld = ""Hello Roslyn!"";
}
}");
Console.WriteLine(ScriptEngine.Execute("new Demo().HelloWorld"));
Console.ReadKey();
}
}
}
A program kimenete:
Hello Roslyn!
A C# szkript használatáról és lehetőségeiről a https://visualstudiomagazine.com/articles/2011/11/16/the-roslyn-scripting-api.aspx címen olvashatunk többet.
Lua
A Lua kifejezetten arra lett kifejlesztve, hogy C nyelven íródott programokba beágyazzák és együttműködjön velük. Ennek ellenére önállóan is alkalmazható. Leginkább a funkcionális programozási paradigmákat támogatja. Az objektumokat szótárszerűen (Dictionary) kezeli. A nyelv megtanulásához egy jó kezdőpont: https://www.tutorialspoint.com/lua/
Az összes Lua implementáció egy állapotgép létrehozásával indul. Ez felel a Lua utasítások végrehajtásáért és a futtató programmal való kommunikációért. .NET programok esetén az NLua implementáció érthető el, ami követi a hivatalos C/C++ nyelvekhez kiadott Lua implementációk filozófiáját. A csomag szintén nugetből telepíthető.
Az alábbi példaprogram az NLua használatát mutatja be:
using NLua;
using System;
namespace DLRLua
{
class Program
{
static void Main(string[] args)
{
Lua state = new Lua();
object[] results = state.DoString("return \"Hello Lua\"");
foreach (var result in results)
{
Console.WriteLine(result);
}
Console.ReadKey();
}
}
}
A program kimenete:
Hello Lua
A Lua állapotgépet leíró osztály neve nemes egyszerűséggel Lua. A példányosítása után a DoString metódussal tudunk szkriptet futtatni. Ennek több visszatérési értéke is lehet, amiket egy tömbben ad vissza.
Az NLua használatáról és lehetőségeiről a projekt weboldalán, a http://nlua.org/ címen olvashatunk.
F#
Az F# egy teljesen funkcionális programozásra tervezett .NET kompatibilis nyelv, amit a Microsoft fejleszt. Alapjául az ML és OCaml nyelv szolgált, valamint kialakítására hatással volt még a C# és a Python is. A nyelv alapvetően funkcionális, de erősen épít az objektum orientáltságra. A nyelv megtanulásához egy jó kezdőpont: https://docs.microsoft.com/en-us/dotnet/fsharp/
Az F# használatához a FSharp.Compiler.Service csomagot kell telepítenünk NuGet segítségével. Ezek után alkalmazhatjuk a programunkban. Az F# beágyazása az előző példákhoz képes kicsivel bonyolultabb, mert nem csak visszatérési értékek szintjén, hanem konzol kimeneten keresztül is kommunikál a programunkkal. Ez azt jelenti, hogy a futtatókörnyezet definiál egy hiba kimenetet és egy normál szöveges kimenetet is, amit a programunkból olvasnunk kell.
A kimenet kényelmes, egyszerű olvasásához készítettem egy EventRaisingTextWriter osztályt, ami a TextWriter alaposztály író metódusait definiálja felül olyan módon, hogy az írás nem egy Stream objektumba történik, hanem esemény keletkezik, ha az adatfolyamba valami új adat kerülne.
Szintén az egyszerű implementálás miatt készítettem egy IDLREvaluator interfészt, ami az IDisposable interfészből öröklődik. Erre azért van szükség, mert a TextWriter osztály és leszármazottjai is megvalósítják az IDisposable interfészt.
Az alábbi példakód egy F# értelmezőt valósít meg:
using DLRCommon;
using FSharp.Compiler.Interactive;
using Microsoft.FSharp.Core;
using System;
using System.IO;
namespace DLRFSharp
{
public sealed class FSharpEvaluator: IDLREvaluator
{
private EventRaisingTextWriter _errorWriter;
private EventRaisingTextWriter _consoleWriter;
private StringReader _instream;
private Shell.FsiEvaluationSessionHostConfig config;
private Shell.FsiEvaluationSession session;
public FSharpEvaluator()
{
config = Shell.FsiEvaluationSession.GetDefaultConfiguration();
var arguments = new string[] { "--noninteractive" };
_instream = new StringReader("");
_errorWriter = new EventRaisingTextWriter();
_consoleWriter = new EventRaisingTextWriter();
_consoleWriter.StreamWritten += _consoleWriter_StreamWritten;
_errorWriter.StreamWritten += _errorWriter_StreamWritten;
session = Shell.FsiEvaluationSession.Create(config, arguments, _instream, _consoleWriter, _errorWriter, null, null);
}
private void _errorWriter_StreamWritten(object sender, string e)
{
ErrorWritten?.Invoke(this, e);
}
private void _consoleWriter_StreamWritten(object sender, string e)
{
ConsoleWritten?.Invoke(this, e);
}
public event EventHandler<string> ConsoleWritten;
public event EventHandler<string> ErrorWritten;
public void Dispose()
{
if (session != null)
{
(session as IDisposable)?.Dispose();
session = null;
}
if (_consoleWriter != null)
{
_consoleWriter.Dispose();
_consoleWriter = null;
}
if (_errorWriter != null)
{
_errorWriter.Dispose();
_errorWriter = null;
}
if (_instream != null)
{
_instream.Dispose();
_instream = null;
}
}
public dynamic Evaluate(string expression)
{
FSharpOption<Shell.FsiValue> result = session.EvalExpression(expression);
if (result?.Value != null)
{
return result.Value.ReflectionValue;
}
return "Nincs visszateresi ertek";
}
}
}
Az értelmező munkamenetet a Shell.FsiEvaluationSession.Create hívás hozza létre. A kód nagy része az ehhez szükséges osztályok létrehozását és beállítását végzi el. A --noninteractive paraméter azt mondja meg az értelmezőnek, hogy nem interaktív módban fut, vagyis ne próbáljon a konzol bemenetről adatot olvasni.
Kódot a munkameneten keresztül az EvalExpression metódussal tudunk futtatni. Ennek visszatérési értéke egy F# specifikus típus lesz, amiből a .NET típust a Value tulajdonságának ReflectionValue tulajdonságán keresztül érjük el.
Az alábbi példaprogram az elkészült értelmező használatát mutatja be:
using DLRCommon;
using System;
namespace DLRFSharp
{
class Program
{
static void Main(string[] args)
{
using (IDLREvaluator fsharp = new FSharpEvaluator())
{
fsharp.ConsoleWritten += Fsharp_ConsoleWritten;s
fsharp.ErrorWritten += Fsharp_ErrorWritten;
dynamic output = fsharp.Evaluate("1 + 41 * 2");
if (output != null)
{
Console.WriteLine("Result: {0}", output);
}
fsharp.Evaluate("printfn \"Hello F#!\"");
}
Console.ReadKey();
}
private static void Fsharp_ErrorWritten(object sender, string e)
{
Console.Write(e);
}
private static void Fsharp_ConsoleWritten(object sender, string e)
{
Console.Write(e);
}
}
}
A program kimenete:
al it : int = 83
Result: 83
Hello World from F#!
val it : unit = ()
Python
A Python nyelvet 1991-ben azért hozták létre, hogy a programozás könnyen elsajátítható legyen, ne kelljen kezdő programozóknak a C "szörnyűségeivel" foglalkoznia. Objektumorientált, funkcionális és procedurális paradigmákat is támogat a nyelv. Két fő nyelvjárása van. Jelenleg a 3.x verzió a legnépszerűbb nyelvjárás, de a 2.x verzió is igen sok helyen használt. A két verzió többé-kevésbé kompatibilis egymással, de vannak lényeges eltérések is.
A Python .NET keretrendszeren futó változata az IronPython. Ebből a legfrissebb, letölthető változat Python 2.x kompatibilis. Azonban készülőben van a 3.x változattal kompatibilis változat is, ami egyelőre fejlesztés alatt áll és csak forráskódból érhető el. Ez megtalálható a https://github.com/IronLanguages/ironpython3 címen.
A szükséges csomagok szintén megtalálhatóak Nuget-en, a csomag, amit telepítenünk kell, az IronPython nevet viseli. Az F# használatához hasonlóan itt is szükségünk lesz konzol átirányításra, ezért itt is felhasználtam az IDLREvaluator interfészt és az EventRaisingTextWriter osztályt.
Az alábbi példakód egy Python értelmezőt valósít meg:
using DLRCommon;
using IronPython;
using IronPython.Hosting;
using Microsoft.Scripting;
using Microsoft.Scripting.Hosting;
using System;
using System.Collections.Generic;
using System.IO;
namespace DLRPython
{
public sealed class PythonEvaluator: IDLREvaluator
{
private ScriptEngine _engine;
private EventRaisingTextWriter _errorWriter;
private EventRaisingTextWriter _consoleWriter;
private ScriptScope _scope;
private MemoryStream _stream;
public event EventHandler<string> ConsoleWritten;
public event EventHandler<string> ErrorWritten;
public PythonEvaluator()
{
var options = new Dictionary<string, object>
{
["DivisionOptions"] = PythonDivisionOptions.New
};
_errorWriter = new EventRaisingTextWriter();
_consoleWriter = new EventRaisingTextWriter();
_consoleWriter.StreamWritten += _consoleWriter_StreamWritten;
_errorWriter.StreamWritten += _errorWriter_StreamWritten;
_stream = new MemoryStream();
_engine = Python.CreateEngine(options);
_engine.Runtime.IO.SetOutput(_stream, _consoleWriter);
_engine.Runtime.IO.SetErrorOutput(_stream, _errorWriter);
_scope = _engine.CreateScope();
}
private void _errorWriter_StreamWritten(object sender, string e)
{
ErrorWritten?.Invoke(this, e);
}
private void _consoleWriter_StreamWritten(object sender, string e)
{
ConsoleWritten?.Invoke(this, e);
}
public dynamic Evaluate(string expression)
{
ScriptSource source = _engine.CreateScriptSourceFromString(expression, SourceCodeKind.AutoDetect);
return source.Execute(_scope);
}
public void Dispose()
{
if (_stream != null)
{
_stream.Dispose();
_stream = null;
}
if (_consoleWriter != null)
{
_consoleWriter.Dispose();
_consoleWriter = null;
}
if (_errorWriter != null)
{
_errorWriter.Dispose();
_errorWriter = null;
}
}
}
}
A kódban, hasonlóan az F# használatához, konfigurálni kell az értelmezőt, amit a Python.CreateEngine() valósít meg. Az értelmezőnek szüksége van egy globális scope-ra a működéshez, ezt a _engine.CreateScope() hívás hozza létre. Ebben a scope-ban tudunk metódusokat, osztályokat, változókat és utasításokat létrehozni. A CreateScriptSourceFromString hívás előkészíti, az Execute pedig végrehajtja a kódrészletet.
Az alábbi példaprogram az elkészült Python értelmező, használatát mutatja be. A kód nagymértékben hasonlít az F#-os változathoz:
using DLRCommon;
using System;
namespace DLRPython
{
class Program
{
static void Main(string[] args)
{
using(IDLREvaluator python = new PythonEvaluator())
{
python.ConsoleWritten += Python_ConsoleWritten;
python.ErrorWritten += Python_ErrorWritten;
string program = "for i in range(0, 10):" +
" print \"IronPython\"";
python.Evaluate(program);
dynamic output = python.Evaluate("3 ** 3");
Console.WriteLine(output);
Console.ReadKey();
}
}
private static void Python_ErrorWritten(object sender, string e)
{
Console.Write(e);
}
private static void Python_ConsoleWritten(object sender, string e)
{
Console.Write(e);
}
}
}
A program kimenete:
IronPython rules
IronPython rules
IronPython rules
IronPython rules
IronPython rules
IronPython rules
IronPython rules
IronPython rules
IronPython rules
IronPython rules
27
-
Az állapotgép egy matematikai modell, amit gyakran alkalmazunk programozásban. Lényege, hogy a megvalósított rendszer egyszerre csak egy állapotban van. Ebből az átmenetek és a további állapotok tevékenységei pontosan meghatározhatók.↩