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.↩