Možná jste se jako já dostali do situace, že jste potřebovali vyzkoušet nějaký kus C# kódu na produkčním prostředí nebo někde jinde, kde prostě není (a není dobré instalovat) Visual Studio a vývojové nástroje. Důvodem může být to, že na produkčním prostředí se kód chová jinak, nebo na vývojovém prostředí nelze nasimulovat stejné podmínky. Tento problém řešíme například tak, že si napíšeme nějakou Consolovku nebo formulářovou aplikaci s testovací tlačítkem apod., a nebo nově využijeme C# Scripting.
C# Scripting
C# Scripting je možnost jak psát scripty v jazyce C#. Vše je uděláno díky .NET Compiler Platform ("Roslyn"), a jako celý Rozlyn, je i C# Scripting opensource. Script, který se píše do souboru s příponou .csx, je tedy zapisován v jazyce C#, je zde ale pár změn oproti klasickému .cs. C# příkazy můžeme psát přímo, nemusí být umístěny ve třídě. Druhou hlavní změnou je to, že v C# scriptu nejsou namespaces. Tyto změny jsou z důvodu, aby se psaní scriptů co možná nejvíce zjednodušilo. Zde vidíme jednoduchý příklad C# a C# Script:
using System;
class Program
{
static void Main()
{
Console.WriteLine("Hello World!");
}
} |
using System;
Console.WriteLine("Hello World!");
|
C# Interactive
Další součástí C# Scripting je command-line nástroj csi - CSharp Interactive. Jedná se o tzv. REPL (read eval print loop) nástroj, který umožňuje zadávat C# příkazy a rovnou je po odeslání vykonávat. Csi dále umožňuje psát některé speciální vestavěné příkazy jako #help – nápověda, #r – načtení reference assemby, atd. Csi.exe je součástí instalace MSBuild. Spustit ho na windows (csi je napsán v .NET Core, takže je dostupný i na Linux, MacOS) můžeme buď přímo z Developer Command Prompt, nebo z cesty “C:\Program Files (x86)\MSBuild\14.0\Bin\” (pro VS2015).
Po spuštění csi, již máme načtené některé reference a některé using importy jako using System a další, a tak můžeme rovnou použít např. třídu Console. Definice těchto referencí a using je uvedena v tzv. Response souboru (pro csi konkrétně C:\Program Files (x86)\MSBuild\14.0\Bin\csi.rsp).
Csi také umožňuje spouštět celé .csx script soubory, spuštěním příkazu #load, nebo uvedením .csx souboru při spouštění csi:
csi.exe script-file.csx
Visual Studio C# Interactive Window
Součástí Visual Studia (od VS2015 Update 1) máme nové C# Interactive window, které obsahuje to samé REPL prostředí jako csi. Díky tomu, že je ale postaveno na Visual Studio editoru, tak obsahuje integrovaný IntelliSense, syntax-coloring atd., podobně jako editor .csx souborů.
Pokud máme ve VS otevřený .csx script, můžeme jeho příkazy spouštět v Interactive window, pomoci volby Execure in interactive (Ctrl+E, Ctrl+E).
Více o C# Interactive si můžete prostudovat např. zde:
https://github.com/dotnet/roslyn/wiki/C%23-Interactive-Walkthrough
http://dailydotnettips.com/2016/01/12/use-c-interactive-window-for-your-coding-experiment-in-visual-studio-2015/
https://msdn.microsoft.com/en-us/magazine/mt614271.aspx
Spuštění C# scriptu
Zpátky ale k našemu využití tj. máme napsaný C# script s testovacím kódem a potřebujeme ho spustit na nějakém jiném než vývojovém prostředí, kde nemáme VS ani C# Interactive (csi) nainstalován.
Jedna z možností je ručně si nahrát na tento počítač již zmíněný tool csi a script spustit pomoci něho. Jediná prerequisita je plný .NET Framework alespoň v 4.6 nebo vyšší. Potom stačí do nějakého adresáře nahrát následující soubory z vývojového počítače z “C:\Program Files (x86)\MSBuild\14.0\Bin\”:
csi.exe
csi.rsp
Microsoft.CodeAnalysis.CSharp.dll
Microsoft.CodeAnalysis.CSharp.Scripting.dll
Microsoft.CodeAnalysis.dll
Microsoft.CodeAnalysis.Scripting.dll
System.AppContext.dll
System.Collections.Immutable.dll
System.IO.FileSystem.dll
System.IO.FileSystem.Primitives.dll
System.Reflection.Metadata.dll
a spustit csi.exe.
C# Scripting API
Druhou možností je využít C# Scripting API. C# Scripting má totiž ještě jednu součást, o které zatím nebyla řeč, a tou je programové Hosting API. To nám umožňuje hostovat C# script engine a přes něj z našeho programu spouštět kusy C# Script kódu.
Pro použití potřebujeme nainstalovat NuGet balíček Microsoft.CodeAnalysis.CSharp.Scripting:
Install-Package Microsoft.CodeAnalysis.CSharp.Scripting
Dále použijeme using Microsoft.CodeAnalysis.CSharp.Scripting, ve kterém je umístěna třída CSharpScript. Pomoci ní již můžeme vyhodnocovat C# výrazy (EvaluateAsync), nebo zkompilovat a spustit C# script (RunAsync). Příklady jsou uvedeny na stránce Scripting API Samples.
S využitím C# Scripting API jsem si napsal vlastní jednoduchou WPF aplikaci, která umožnuje spustit libovolný C# Script kód.
Hlavní kód aplikace je obsluha tlačítka Run pro spuštění scriptu a odchycení výsledků z console do textboxu txtConsole. Kód je umístěn v souboru MainWindow.cs:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Text;
using System.IO;
using Microsoft.CodeAnalysis.CSharp.Scripting;
namespace CSharpScripting
{
#region ControlWriter
public class ControlWriter : TextWriter
{
private readonly TextBox textbox;
public ControlWriter(TextBox textbox)
{
this.textbox = textbox;
}
public override void Write(char value)
{
textbox.Text += value;
}
public override void Write(string value)
{
textbox.Text += value;
}
public override Encoding Encoding
{
get { return Encoding.ASCII; }
}
}
#endregion
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Console.SetOut(new ControlWriter(txtConsole));
this.Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
System.Windows.Input.Keyboard.Focus(txtScript);
}
private async void ButtonRun_Click(object sender, RoutedEventArgs e)
{
ButtonRun.IsEnabled = false;
this.Cursor = System.Windows.Input.Cursors.Wait;
try
{
txtConsole.Text = "";
var scriptRunner = CSharpScript.Create(txtScript.Text, GetScriptOptions()).CreateDelegate();
await scriptRunner.Invoke();
}
catch (Microsoft.CodeAnalysis.Scripting.CompilationErrorException ex)
{
txtConsole.Text = txtConsole.Text + (txtConsole.Text.Length == 0 ? "" : Environment.NewLine) + string.Join(Environment.NewLine, ex.Diagnostics);
}
catch (Exception ex)
{
txtConsole.Text = txtConsole.Text + (txtConsole.Text.Length == 0 ? "" : Environment.NewLine) + string.Join(Environment.NewLine, ex.ToString());
}
finally
{
ButtonRun.IsEnabled = true;
this.Cursor = null;
}
}
private Microsoft.CodeAnalysis.Scripting.ScriptOptions GetScriptOptions()
{
return Microsoft.CodeAnalysis.Scripting.ScriptOptions.Default
.WithReferences(typeof(System.Diagnostics.Process).Assembly, //System Assembly
typeof(System.Dynamic.DynamicObject).Assembly, //System.Core Assembly
typeof(Microsoft.CSharp.RuntimeBinder.Binder).Assembly) //Microsoft.CSharp Assembly
.WithImports(new[] {
"System",
"System.IO",
"System.Collections.Generic",
"System.Diagnostics",
"System.Dynamic",
"System.Linq",
"System.Linq.Expressions",
"System.Text",
"System.Threading.Tasks"
});
}
}
}
Povšimněte si funkce GetScriptOptions, která se stará o to, aby vrátila nastavení pro CSharpScript.Create. Zde řeším přidání některých referencí a using importů, aby byly automaticky pro script k dispozici (podobně jako v csi nebo VS C# Interactive).
Zdrojové soubory celé aplikace i zkompilovanou release verzi naleznete na GitHubu holajan/CSharpScripting.
Jako zdroj zabývající se C# scripting dále doporučuji první část přednášky přímo od jednoho z autorů C# scriptingu:
DotNetCast -
Tomáš Matoušek: Interactive Development with C# (v CZ)
.