Skip to content

CS Script Overview

Oleg Shilo edited this page Oct 3, 2022 · 20 revisions

This Wiki documentation in most of the cases is fully sufficient for the development with CS-Script. It reflects the current state of CS-Script and all most recent API changes. Though in some cases (e.g. legacy topics) you may find it useful to consult the legacy online documentation.


Further reading:

CS-Script Overview

CS-Script compared to other scripting solutions

When you start exploring CS-Script you will probably quickly notice that it is quite different comparing to other scripting solutions. The immediate candidates for comparison is Roslyn. It concentrates mostly on hosting the script engine and executing small pieces of code. Its documentation offers plenty of samples of executing simple statements like '1+2' or 'Console.WriteLine("Hello")'. It all makes an impression that Roslyn is designed with a single objective in mind - providing a hosting solution for execution of user specified C# fragments (e.g. REPL or a workflow engine). The usual consideration scope of Roslyn is often no larger than a single C# statement. Thus, it offers no solution for:

  • Executing a complete C# module (C# file).
  • Executing C# file as an external process (e.g. from command-prompt).
  • Linking multiple inter-dependent C# files at runtime and executing them as a single unit.
  • Convenient way of referencing dependency assemblies.
  • Debugging scripts.
  • An almost 20 years old (since 2002) problem of memory leaking with every single C# statement executed in the current AppDomain.
  • ...

Roslyn is fully capable of allowing to build derived solutions addressing the challenges listed above but it does not deal with them directly.

CS-Script is different as its objective is to provide a platform for executing scripts of any level of complexity and dependency. As well as extending applications with the functionality as rich as .NET can facilitate. In a way CS-Script to C# syntax is what Python platform is to Python syntax.

CS-Script does answer the all-scripting challenges including REPL and IDE. But what is even more important is that CS-Script provides one unified interface (API) that normalizes all three major C# compiling engines dotnet, csc and Roslyn. Thus, CS-Script lets developers have a single scripting codebase and makes the choice of a concrete compiling engine a simple matter of configuration. For the complete comparative overview of the compiling engines, see the dedicated Choosing Compiler Engine page.

An interesting scripting solutions comparison discussion took place in thread #309. You can find this summary useful when choosing scripting technology.

Background

CS-Script is one of the most mature C# scripting frameworks available today. CS-Script first public release is dated back to 2004. At that time .NET ecosystem didn't provide any scripting solution. What is even more intriguing, that at that time Microsoft consistently refused any call for such a scripting solution. It insisted that there is no need for scripting as it has no place in .NET domain. Thus, CS-Script was a pioneering effort to bring scripting experience to the developers when .NET vendor failed to do so. The main idea of CS-Script is to allow plain vanilla C# code execution from both CLI and form any CLR application hosting the script engine.

Things have changed since then. Today Microsoft is behind the ongoing Roslyn effort - their own compiler-as-service solution. Mono also has its compiler-as-service (Mono.CSharp) released around 2010 (discontinued since .NET5 arrival). Though CS-Script remains one of the best choices of scripting platform due to the unorthodox conceptual approaches and unique features not found in other solutions.

What makes CS-Script so different is the comprehensive approach to scripting as a software development technique that has various applications (beyond just simple "eval"). CS-Script was hugely influenced by Python. In fact, many CS-Script features were "borrowed" from Python: referencing external scripts and assemblies, caching, converting scripts into self-sufficient executables.

And similarly to Python, CS-Script provides the answer for two conceptual scripting use cases:

  • script file execution by the stand alone script engine
  • hosted script (file or in-memory text) execution by the script engine hosted by a user application.

For the hosted script execution CS-Script provides one unified interface, which allows switching between underlying compiling services (csc, dotnet and Roslyn) without the need to change the hosting code.

CSScript.EvaluatorConfig.Engine = EvaluatorEngine.Roslyn;
                                //EvaluatorEngine.Mono;
                                //EvaluatorEngine.CodeDom;

var sqr = CSScript.Evaluator
                  .CreateDelegate(@"int Sqr(int a)
                                    {
                                        return a * a;
                                    }");

var r = sqr(3);

Script CLI execution

Additional documentation

The simplest way to execute the C# script is to run the script engine executable (css.exe) and pass the script file as a parameter. The script file is a file containing an ordinary ECMA-compliant C# code. CS-Script currently targets .NET 5 and above implementation of CLR. CS-Script implements script caching that ensures that the script file is never compiled unless it has changed since the first execution or it is the first execution. This in turn allows a remarkable performance for the script execution, which is identical to the performance of the fully compiled assembly. Thus, CS-Script implements JIT compilation but not within the scope of the code fragments (e.g. .NET JIT compilation) but within the scope of whole script file.

These are only some of the important features of CS-Script:

  • Including (referencing) dependency scripts from the main script.
  • Referencing external assemblies from the script.
  • Automatic resolving (downloading and referencing) NuGet packages.
  • Possibility to plug in external compiling services for supporting alternative script syntax (e.g. VB, C++).
  • Scripts can be executed on any OS that can host .NET5 (Windows, Linux and OSX).
  • Full integration with Visual Studio, VSCode, Sublime Text 3 and Notepad++ (CS-Script extension for Notepad++ brings true IntelliSense to the 'people's editor').
  • ...

The complete list of the features can be found here,

You can find all the details about CLI script execution for the older versions in the legacy Online documentation .

The default CS-Script distribution includes various components that make the scripting experience comfortable and yet it in many cases a single script engine file is sufficient for the scripting execution.

The most convenient way of installing CS-Script is to get it from Chocolatey (Windows equivalent of Linux apt-get). And you can install CS-Script Debian package on Linux by following the setup instructions from the Releases page.

Hosted script execution

Additional documentation

CS-Script can be hosted by any CLR application. The easiest way to do this is to add it as a NuGet package. CS-Script allows executing entire script files or C# code fragments. And similarly to the CLI script execution user can choose the compilation engine (csc or Roslyn). The choice can be dictated by the performance, deployment and maintainability considerations. The convenience of the hosting API is not one of the considerations as it is the same for all compiler engines. This is because CS-Script encapsulates all three supported C# compilers into a single unified interface CSScript.Evaluator. Thus, changing the compiling engine becomes a merely configuration exercise.

NOTE: By default CSScript.Evaluator always returns a new instance of the evaluator, which implements IEvaluator interface (e.g. RoslynEvaluator). If this behavior is undesired change the evaluator access policy by setting CSScript.EvaluatorConfig.Access value.

The evaluator allows executing either code fragments or entire class(es) definitions. The script can automatically access host types without any restrictions except type's visibility (public vs. private). Scripted objects can be accessed:

  • dynamically (via 'dynamic')
  • statically via the interface
  • duck-typing
  • via strongly-typed delegates:

This is the example of accessing Add method that is implemented as a script:

Precondition: the host application defines ICalc interface

public interface ICalc
{
    int Add(int a, int b);
}

Accessing the script members dynamically:

dynamic script = CSScript.Evaluator
                         .LoadCode(@"using System;
                                     public class Script
                                     {
                                         public int Add(int a, int b)
                                         {
                                             return a + b;
                                         }
                                     }");

int result = script.Add(1, 2);

Accessing the script members statically:

ICalc script = CSScript.Evaluator
                       .LoadCode<ICalc>(@"using System;
                                          public class Script : ICalc
                                          {
                                              public int Add(int a, int b)
                                              {
                                                  return a + b;
                                              }
                                          }");

int result = script.Add(1, 2);

Loading strongly typed delegate:

var Add = CSScript.Evaluator
                  .LoadDelegate<Func<int, int, int>>(
                             @"int Add(int a, int b)
                               {
                                   return a + b;
                               }");
int result = Add(3, 2);

Duck-typing (interface alignment)

Note that class Script doesn't implement ICalc. Instead, the script engine wraps the script object into dynamically generated proxy of ICalc type:

ICalc script = CSScript.Evaluator
                       .LoadCode<ICalc>(@"using System;
                                          public class Script
                                          {
                                              public int Add(int a, int b)
                                              {
                                                  return a + b;
                                              }
                                          }");

int result = script.Add(1, 2);