for .NET 4.52 and later
Get it from Nuget:
Install-Package Westwind.Scripting
(currently you need to use the -IncludePreRelease
flag)
The small CSharpScripting
class provides an easy way to compile and execute C# on the fly from source code at runtime using the .NET compiler services on full Framework .NET. You can use Roslyn compilation for the latest C# features, or classic C# 5 features.
This class makes is very easy to integrate simple scripting or text merging features into applications with minimal effort.
This library provides:
Execution Features
ExecuteCode()
- Execute an arbitrary block of codeEvaluate()
- Evaluate an expression from a code stringExecuteMethod()
- Execute one or more methods from sourceCompileClass()
- Generate a class instance from C# code
Supported features
- Assembly Caching so not every execution generates a new assembly
- Ability to compile entire classes and execute them
- Automatic Assembly Cleanup at shutdown
- Use Roslyn or Classic C# compiler interchangeably
- Display errors and source and line numbers
If you want to use Roslyn compilation for the latest C# features you have to make sure you add the
Microsoft.CodeDom.CompilerServices
NuGet Package to your application's root project to provide the required compiler binaries for your application.Note that this adds a sizable chunk of files to your application's output folder in the
\roslyn
folder. If you don't want this you can use the classic compiler, at the cost of not having access to C# 6+ features.
Using the CSharpScriptExecution
class is very easy. It works with code passed as strings for either a Code block, expression, one or more methods or even as a full C# class that can be turned into an instance.
You can add Assembly References and Namespaces via the AddReferece()
and AddNamespace()
methods.
Script Execution is gated rather than throwing directly to provide
A code snippet can be any block of .NET code that can be executed. You can pass any number of parameters to the snippets which are accessible via a parameters
object array. You can optionally return
a value by providing a return
statement.
var script = new CSharpScriptExecution()
{
SaveGeneratedCode = true,
CompilerMode = ScriptCompilerModes.Roslyn
};
script.AddDefaultReferencesAndNamespaces();
//script.AddAssembly("Westwind.Utilities.dll");
//script.AddNamespace("Westwind.Utilities");
var code = $@"
// Check some C# 6+ lang features
var s = new {{ name = ""Rick""}}; // anonymous types
Console.WriteLine(s?.name); // null propagation
// pick up and cast parameters
int num1 = (int) @0; // same as parameters[0];
int num2 = (int) @1; // same as parameters[1];
// string templates
var result = $""{{num1}} + {{num2}} = {{(num1 + num2)}}"";
Console.WriteLine(result);
return result;
";
string result = script.ExecuteCode(code,10,20) as string;
Console.WriteLine($"Result: {result}");
Console.WriteLine($"Error: {script.Error}");
Console.WriteLine(script.ErrorMessage);
Console.WriteLine(script.GeneratedClassCodeWithLineNumbers);
Assert.IsFalse(script.Error, script.ErrorMessage);
Assert.IsTrue(result.Contains(" = 30"));
Note that the return
in your code snippet is optional - you can just run code without a result value.
Any parameters you pass in can be accessed either via
parameters[0]
,parameters[1]
etc. or using a simpler string representation of@0
,@1
.
If you want to evaluate a single expression, there's a shortcut Evalute()
method that works pretty much the same:
var script = new CSharpScriptExecution()
{
SaveGeneratedCode = true,
};
script.AddDefaultReferencesAndNamespaces();
// Full syntax
//object result = script.Evaluate("(decimal) parameters[0] + (decimal) parameters[1]", 10M, 20M);
// Numbered parameter syntax is easier
object result = script.Evaluate("(decimal) @0 + (decimal) @1", 10M, 20M);
Console.WriteLine($"Result: {result}");
Console.WriteLine($"Error: {script.Error}");
Console.WriteLine(script.ErrorMessage);
Console.WriteLine(script.GeneratedClassCode);
Assert.IsFalse(script.Error, script.ErrorMessage);
Assert.IsTrue(result is decimal, script.ErrorMessage);
This method is a shortcut wrapper and simply wraps your code into a single line return {exp};
statement.
ExecuteCode()
and Evaluate()
are shortcuts for the slightly lower level and more flexible ExecuteMethod()
method which as the name implies allows you to specify a single or multiple methods. In fact you can provide an entire class body including properties, events and nested class definitions in the code passed in. This gives a lot of flexibility as you can properly type parameters and return types:
var script = new CSharpScriptExecution()
{
SaveGeneratedCode = true,
CompilerMode = ScriptCompilerModes.Roslyn,
GeneratedClassName = "HelloWorldTestClass"
};
script.AddDefaultReferencesAndNamespaces();
string code = $@"
public string HelloWorld(string name)
{{
string result = $""Hello {{name}}. Time is: {{DateTime.Now}}."";
return result;
}}";
string result = script.ExecuteMethod(code,"HelloWorld","Rick") as string;
Console.WriteLine($"Result: {result}");
Console.WriteLine($"Error: {script.Error}");
Console.WriteLine(script.ErrorMessage);
Console.WriteLine(script.GeneratedClassCode);
Assert.IsFalse(script.Error);
Assert.IsTrue(result.Contains("Hello Rick"));
Note that you can provide more than a method in the code block - you can provide an entire class body including additional methods, properties/fields, events and so on. Effectively you can build out an entire class this way. After intial execution you can access the ObjectInstance
member and use either Reflection or dynamic
to access the functionality on that class.
var script = new CSharpScriptExecution()
{
SaveGeneratedCode = true,
CompilerMode = ScriptCompilerModes.Roslyn
};
script.AddDefaultReferencesAndNamespaces();
// Class body with multiple methods and properties
string code = $@"
public string HelloWorld(string name)
{{
string result = $""Hello {{name}}. Time is: {{DateTime.Now}}."";
return result;
}}
public string GoodbyeName {{ get; set; }}
public string GoodbyeWorld()
{{
string result = $""Goodbye {{GoodbyeName}}. Time is: {{DateTime.Now}}."";
return result;
}}
";
string result = script.ExecuteMethod(code, "HelloWorld", "Rick") as string;
Console.WriteLine($"Result: {result}");
Console.WriteLine($"Error: {script.Error}");
Console.WriteLine(script.ErrorMessage);
Console.WriteLine(script.GeneratedClassCode);
Assert.IsFalse(script.Error);
Assert.IsTrue(result.Contains("Hello Rick"));
// You can pick up the ObjectInstance of the generated class
// Make dynamic for easier access
dynamic instance = script.ObjectInstance;
instance.GoodbyeName = "Markus";
result = instance.GoodbyeWorld();
Console.WriteLine($"Result: {result}");
Assert.IsTrue(result.Contains("Goodbye Markus"));
You can also compile an entire class and then get passed back a dynamic
reference to that class so that you can explicitly use that object:
var script = new CSharpScriptExecution()
{
SaveGeneratedCode = true,
CompilerMode = ScriptCompilerModes.Roslyn
};
script.AddDefaultReferencesAndNamespaces();
var code = $@"
using System;
namespace MyApp
{{
public class Math
{{
public string Add(int num1, int num2)
{{
// string templates
var result = num1 + "" + "" + num2 + "" = "" + (num1 + num2);
Console.WriteLine(result);
return result;
}}
public string Multiply(int num1, int num2)
{{
// string templates
var result = $""{{num1}} * {{num2}} = {{(num1 * num2)}}"";
Console.WriteLine(result);
return result;
}}
}}
}}
";
dynamic math = script.CompileClass(code);
Console.WriteLine(script.GeneratedClassCodeWithLineNumbers);
Assert.IsFalse(script.Error,script.ErrorMessage);
Assert.IsNotNull(math);
string addResult = math.Add(10, 20);
string multiResult = math.Multiply(3 , 7);
Assert.IsTrue(addResult.Contains(" = 30"));
Assert.IsTrue(multiResult.Contains(" = 21"));
This library is published under MIT license terms.
Copyright © 2012-2019 Rick Strahl, West Wind Technologies
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
If you find this library useful, consider making a small donation: