ScriptCS

Pojawienie się kompilatora Roslyn otworzyło wiele nowych ścieżek.

Jedną z tych ścieżek jest biblioteka ScriptCS.

Dzięki niej jest możliwe używanie C# jako języka skryptowego w konsoli. Dzięki niemu edytory takie jak Notepad++ mogą kompilować kod napisany w C#.

Co najważniejsze można użyć tej biblioteki do własnych celów.

Bibliotekę można pobrać z serwisu NuGet.

image

Czy kod jest skomplikowany? Nie

Tworzysz instancje obiektu “ScriptEngine”. Potem on tworzy sesje. Później do sesji dodajesz odpowiednie referencje do biblioteki i już możesz wywoływać kod w C# jakby był on skryptem.

using Roslyn.Scripting.CSharp;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            var scriptEngine = new ScriptEngine();
            var session = scriptEngine.CreateSession();

            session.AddReference("System");
            session.AddReference("System.Core");
            session.Execute(@"public int six() { return 6; }");

            int six = session.Execute<int>("six()");
        }
    }
}

Pomyślałem sobie, jak można wykorzystać skryptowość w jakimś programie. Do głowy przyszła mi prosta aplikacja kalkulator. Kalkulator ten tak naprawdę będzie wykonywać kod napisany w C# i zwracać wynik działania metod.

Sama aplikacja nie jest doskonała, ale chodzi o przykład użycia dynamicznego kodu tekstowego w C#.

image_thumb2

Wygląd aplikacji jest zawarty w tym pliku XAML.

<Window x:Class="Calculator.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Calculator"
        mc:Ignorable="d"
        Title="MainWindow" Height="350.333" Width="641.333">
    <Grid Background="#FF383838" >
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="12*"/>
            <ColumnDefinition Width="419*"/>
            <ColumnDefinition Width="26*"/>
            <ColumnDefinition Width="87*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="12*"/>
            <RowDefinition Height="83*"/>
            <RowDefinition Height="224*"/>
        </Grid.RowDefinitions>
        <TextBox Margin="0,10,85,10" Grid.Column="1" IsReadOnly="True" Grid.Row="1" FontSize="21.333" Foreground="#FF9B6161" x:Name="txtCode" Grid.ColumnSpan="3" />

        <Grid Margin="0,10,85,10" Grid.Row="2" Grid.ColumnSpan="3" Grid.Column="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="5*"/>
                <RowDefinition Height="5*"/>
                <RowDefinition Height="5*"/>
                <RowDefinition Height="5*"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="94*"/>
                <ColumnDefinition Width="95*"/>
                <ColumnDefinition Width="95*"/>
                <ColumnDefinition Width="95*"/>
                <ColumnDefinition Width="95*"/>
                <ColumnDefinition Width="95*"/>
            </Grid.ColumnDefinitions>
            <Button  Content="SIN" Click="AddTextButtonFunc_Click" Background="#FFFF9696" BorderBrush="{x:Null}" />
            <Button Content="1" Grid.Row="1"  Click="AddTextButton_Click" Background="#FFE6FFFD" BorderBrush="{x:Null}"  />
            <Button  Content="4" Grid.Row="2"  Click="AddTextButton_Click" Background="#FFE6FFFD" BorderBrush="{x:Null}"   />
            <Button Content="7" Grid.Row="3"  Click="AddTextButton_Click" Background="#FFE6FFFD" BorderBrush="{x:Null}" />
            <Button  Content="COS"  Grid.Column="1" Click="AddTextButtonFunc_Click" Background="#FFFF9696" BorderBrush="{x:Null}" />
            <Button  Content="2" Grid.Row="1"  Grid.Column="1"  Click="AddTextButton_Click" Background="#FFE6FFFD" BorderBrush="{x:Null}"  />
            <Button  Content="5" Grid.Row="2"  Grid.Column="1"  Click="AddTextButton_Click" Background="#FFE6FFFD" BorderBrush="{x:Null}"  />
            <Button  Content="8" Grid.Row="3" Grid.Column="1" Click="AddTextButton_Click" Background="#FFE6FFFD" BorderBrush="{x:Null}"  />
            <Button  Content="TANG"  Grid.Column="2" Click="AddTextButtonFunc_Click" Background="#FFFF9696" BorderBrush="{x:Null}"  />

            <Button  Content="3" Grid.Row="1"  Grid.Column="2"  Click="AddTextButton_Click" Background="#FFE6FFFD" BorderBrush="{x:Null}"  />
            <Button  Content="5" Grid.Row="2"  Grid.Column="2" Click="AddTextButton_Click" Background="#FFE6FFFD" BorderBrush="{x:Null}"   />
            <Button  Content="9" Grid.Row="3" Grid.Column="2" Click="AddTextButton_Click" Background="#FFE6FFFD" BorderBrush="{x:Null}"  />


            <Button Content="CTANG"  Grid.Column="3" Click="AddTextButtonFunc_Click" Background="#FFFF9696" BorderBrush="{x:Null}"  />
            <Button  Content="=" Grid.Row="1"  Grid.Column="3" Click="EqualBtnClick" Background="#FFF0FF93" BorderBrush="{x:Null}"   />
            <Button  Content="Clear" Grid.Row="2"  Grid.Column="3" Click="ClearButtonClick" Background="#FFF0FF93" BorderBrush="{x:Null}"  />
            <Button Tag="+" Content="Plus"  Grid.Column="4" Click="PlusButtonClick" Background="#FFDFFFCD" BorderBrush="{x:Null}"  />
            <Button  Tag="-" Content="Minus"  Grid.Column="4" Click="PlusButtonClick" Grid.Row="1" Background="#FFDFFFCD" BorderBrush="{x:Null}"  />
            <Button  Tag="*" Content="Mnoż"  Grid.Column="4" Click="PlusButtonClick" Grid.Row="2" Background="#FFDFFFCD" BorderBrush="{x:Null}"  />
            <Button  Tag="/" Content="Dziel"  Grid.Column="4" Click="PlusButtonClick" Grid.Row="3" Background="#FFDFFFCD" BorderBrush="{x:Null}"  />
            <Button  Content="LOG_10" Click="AddTextButtonFunc_Click" Background="#FFFF9696" BorderBrush="{x:Null}" Grid.Column="5" />
            <Button  Content="LOG" Click="AddTextButtonFunc_Click" Background="#FFFF9696" BorderBrush="{x:Null}" Grid.Column="5" Grid.Row="1" />
            <Button  Content="PI" Tag=" PI " Click="AddTextButtonStatic_Click" Background="#FFE6FFFD" BorderBrush="{x:Null}" Grid.Column="5" Grid.Row="2" />
            <Button  Content="0" Grid.Row="3" Grid.Column="3" Click="AddTextButton_Click" Background="#FFE6FFFD" BorderBrush="{x:Null}"  />

        </Grid>

    </Grid>
</Window>

Serce pomysłu aplikacji leży tutaj.

Stworzę sesję skryptową i dodam do niej takie metody  jak SIN(), COS(), TANG(), CTANG(), LOG_10, LOG.

Wpisując więc do pola tekstowego polecenie SIN(0) tak naprawdę wykona metodę SIN, którą dodałem do sesji

Oprócz metod dodałem także zmienną PI, która będzie przechowywać wartość PI.

Do sesji musiałem też dodać odpowiednie referencje. W przeciwnym wypadku typ Object i double byłby niezrozumiały dla kompilatora, który wykonuje to polecenia skryptowe.

private void Initialize() 
{ 
    this.rosylnEngine = new ScriptEngine(); 
    this.session = rosylnEngine.CreateSession(); 

    session.AddReference("System"); 
    session.AddReference("System.Core"); 

    session.Execute(@"using System;"); 
    session.Execute(@"public double SIN(double x) {return Math.Sin(x*Math.PI/180));}"); 
    session.Execute(@"public double COS(double x) {return   Math.Cos(x*Math.PI/180);}"); 
    session.Execute(@"public double TANG(double x) {return   Math.Tan(x*Math.PI/180);}"); 
    session.Execute(@"public double CTANG(double x) {return 1/Math.Tan(x*Math.PI/180);}"); 

    session.Execute(@"public double LOG_10(double x) {return Math.Log10(x);}"); 
   
    session.Execute(@"public double LOG(double x) {return  Math.Log(x);}"); 
    session.Execute("double PI = 3.1415926535;"); 
}

Przy starcie aplikacji tworzę wszystkie potrzebne skrypty.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Initialize();
    }

Gdy nacisnę przycisk równa się, treść tekstowa z pola tekstowego zostanie wykonana.

private void EqualBtnClick(object sender, RoutedEventArgs e)
{
    try
    {
        txtCode.Text = txtCode.Text.Replace(',', '.');

        var x = session.Execute(txtCode.Text);

        txtCode.Text = x.ToString();
    }
    catch (Exception)
    {
        Clear();
    }
}

Reszta kodu do obsługi przycisków wygląda tak.

private bool sinbeign = true;
private ScriptEngine rosylnEngine;
private Session session;

private bool sinbeginNoNumber = true;
private bool sigthExist = false;

private void AddTextButton_Click(object sender, RoutedEventArgs e)
{
    var button = (Button)sender;
    sinbeginNoNumber = true;
    txtCode.Text += button.Content;
}
private void AddTextButtonFunc_Click(object sender, RoutedEventArgs e)
{
    string s = "";
    if (sigthExist)
    {
        s = txtCode.Text;
    }

    var f = (Button)sender;

    if (sinbeign)
    {
        txtCode.Text = s + ""+ f.Content+"(";
        sinbeign = false; sinbeginNoNumber = false;
    }
    else
    {
        sinbeign = true;
        if (sinbeginNoNumber)
            txtCode.Text = txtCode.Text  + ")";
        else
            txtCode.Text = txtCode.Text + "0)";
    }

    sigthExist = false;
}


private void PlusButtonClick(object sender, RoutedEventArgs e)
{
    if (!sinbeign)
        return;

    var f = (Button)sender;
    txtCode.Text = txtCode.Text + " " + f.Tag.ToString() + " ";
    sigthExist = true;
}
private void ClearButtonClick(object sender, RoutedEventArgs e)
{ Clear();}

private void Clear()
{
    txtCode.Text = "";
    sigthExist = false;sinbeginNoNumber = true; sinbeign = true;
}

private void AddTextButtonStatic_Click(object sender, RoutedEventArgs e)
{
    var button = (Button)sender;
    if (sigthExist || txtCode.Text == "")
    {
        sinbeginNoNumber = true;
        txtCode.Text += button.Content;
    }
}

Kalkulator działa tak.

pplplp_thumb2

Pod biblioteką ScriptCS kryje się wiele możliwości.

W połączeniu z kompilatorem roslyn jest możliwe stworzenie nawet prostego CMS, który będzie wypluwał dll-ki , w zależności od tego jakie przyciski nacisnął użytkownik.

Używając ScriptCS jest możliwe napisanie aplikacji, która może być rozbudowana w trakcie jej działania.

ScriptCS domyślnie jest oddzielona od środowiska na, którym się uruchamia.

hhhhhhh

Gdy  więc chcemy, by nasz obiekt wewnątrz aplikacji działał z sesją skryptową, musimy go przesłać do metody, która tworzy instancje sesji.