How to Safely Execute Dynamic C# Code at Runtime Using Roslyn

Executing dynamic C# code at runtime can be powerful but also comes with security and performance risks. Microsoft’s Roslyn compiler provides a way to compile and execute C# code dynamically while offering safety mechanisms.

This guide walks through how to use Roslyn to safely evaluate and run C# code at runtime.

Why Use Roslyn for Dynamic Code Execution?

Roslyn enables runtime compilation of C# code, making it useful for:

  • Scripting engines within applications.
  • Plugins and extensibility without recompiling the main application.
  • Interactive debugging and testing scenarios.
  • Custom formula evaluations in applications like rule engines.

Step 1: Install Roslyn Dependencies

To use Roslyn for dynamic execution, install the necessary NuGet packages:

Install-Package Microsoft.CodeAnalysis.CSharp.Scripting
Install-Package Microsoft.CodeAnalysis.Scripting

Step 2: Basic Execution of Dynamic Code

A simple way to execute dynamic C# code using Roslyn:

using System;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;

class Program
{
    static async Task Main()
    {
        string code = "1 + 2";
        var result = await CSharpScript.EvaluateAsync<int>(code);
        Console.WriteLine("Result: " + result);
    }
}

Step 3: Providing Context for Execution

To allow dynamic scripts to use variables and functions from your main program, use a custom script state:

class ScriptGlobals
{
    public int X { get; set; } = 10;
}

var options = ScriptOptions.Default.AddReferences(typeof(ScriptGlobals).Assembly);
string code = "X * 2";
var result = await CSharpScript.EvaluateAsync<int>(code, options, new ScriptGlobals());
Console.WriteLine(result); // Output: 20

Step 4: Handling Exceptions in Dynamic Code

Since executing untrusted code can lead to runtime errors, wrap execution in try-catch:

try
{
    string invalidCode = "int x = 1 / 0;";
    await CSharpScript.EvaluateAsync(invalidCode);
}
catch (CompilationErrorException ex)
{
    Console.WriteLine("Compilation Error: " + string.Join("\n", ex.Diagnostics));
}
catch (Exception ex)
{
    Console.WriteLine("Runtime Error: " + ex.Message);
}

Step 5: Security Considerations

Executing user-provided code can be risky. Follow these best practices:

1. Use a Restricted Execution Context

Limit the namespaces and APIs available to the script:

var options = ScriptOptions.Default
    .AddReferences(typeof(object).Assembly) // Only essential assemblies
    .WithImports("System"); // Restrict available namespaces

2. Limit Execution Time

Run code in a separate thread with a timeout:

using System.Threading;
using System.Threading.Tasks;

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
try
{
    var task = CSharpScript.EvaluateAsync("while(true) {}", cancellationToken: cts.Token);
    await task;
}
catch (OperationCanceledException)
{
    Console.WriteLine("Execution Timed Out");
}

3. Use AppDomain Sandboxing (For Older .NET Versions)

In older .NET Framework applications, AppDomains can be used to isolate script execution. However, .NET Core and later versions no longer support AppDomains.

Step 6: Running More Complex Scripts with State

For multi-line scripts, use RunAsync instead of EvaluateAsync:

string script = @"
int Multiply(int a, int b) => a * b;
return Multiply(3, 4);
";
var result = await CSharpScript.RunAsync(script);
Console.WriteLine(result.ReturnValue); // Output: 12

Conclusion

Roslyn provides a powerful way to execute C# code dynamically while maintaining security and control. By following best practices such as limiting execution scope, handling errors, and enforcing timeouts, you can safely integrate dynamic scripting into your applications without exposing them to excessive risk.

0
781

Related

Removing duplicates from a list in C# is a common task, especially when working with large datasets. C# provides multiple ways to achieve this efficiently, leveraging built-in collections and LINQ.

Using HashSet (Fastest for Unique Elements)

A HashSet<T> automatically removes duplicates since it only stores unique values. This is one of the fastest methods:

List<int> numbers = new List<int> { 1, 2, 2, 3, 4, 4, 5 };
numbers = new HashSet<int>(numbers).ToList();
Console.WriteLine(string.Join(", ", numbers)); // Output: 1, 2, 3, 4, 5

Using LINQ Distinct (Concise and Readable)

LINQ’s Distinct() method provides an elegant way to remove duplicates:

List<int> numbers = new List<int> { 1, 2, 2, 3, 4, 4, 5 };
numbers = numbers.Distinct().ToList();
Console.WriteLine(string.Join(", ", numbers)); // Output: 1, 2, 3, 4, 5

Removing Duplicates by Custom Property (For Complex Objects)

When working with objects, DistinctBy() from .NET 6+ simplifies duplicate removal based on a property:

using System.Linq;
using System.Collections.Generic;

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

List<Person> people = new List<Person>
{
    new Person { Name = "Alice", Age = 30 },
    new Person { Name = "Bob", Age = 25 },
    new Person { Name = "Alice", Age = 30 }
};

people = people.DistinctBy(p => p.Name).ToList();
Console.WriteLine(string.Join(", ", people.Select(p => p.Name))); // Output: Alice, Bob

For earlier .NET versions, use GroupBy():

people = people.GroupBy(p => p.Name).Select(g => g.First()).ToList();

Performance Considerations

  • HashSet<T> is the fastest but only works for simple types.
  • Distinct() is easy to use but slower than HashSet<T> for large lists.
  • DistinctBy() (or GroupBy()) is useful for complex objects but may have performance trade-offs.

Conclusion

Choosing the best approach depends on the data type and use case. HashSet<T> is ideal for primitive types, Distinct() is simple and readable, and DistinctBy() (or GroupBy()) is effective for objects.

0
251

Reading a file line by line is useful when handling large files without loading everything into memory at once.

✅ Best Practice: Use File.ReadLines() which is more memory efficient.

Example

foreach (string line in File.ReadLines("file.txt"))
{
    Console.WriteLine(line);
}

Why use ReadLines()?

Reads one line at a time, reducing overall memory usage. Ideal for large files (e.g., logs, CSVs).

Alternative: Use StreamReader (More Control)

For scenarios where you need custom processing while reading the contents of the file:

using (StreamReader reader = new StreamReader("file.txt"))
{
    string? line;
    while ((line = reader.ReadLine()) != null)
    {
        Console.WriteLine(line);
    }
}

Why use StreamReader?

Lets you handle exceptions, encoding, and buffering. Supports custom processing (e.g., search for a keyword while reading).

When to Use ReadAllLines()? If you need all lines at once, use:

string[] lines = File.ReadAllLines("file.txt");

Caution: Loads the entire file into memory—avoid for large files!

3
280

String interpolation, introduced in C# 6.0, provides a more readable and concise way to format strings compared to traditional concatenation (+) or string.Format(). Instead of manually inserting variables or placeholders, you can use the $ symbol before a string to directly embed expressions inside brackets.

string name = "Walt";
string job = 'Software Engineer';

string message = $"Hello, my name is {name} and I am a {job}";
Console.WriteLine(message);

This would produce the final output of:

Hello, my name is Walt and I am a Software Engineer

String interpolation can also be chained together into a multiline string (@) for even cleaner more concise results:

string name = "Walt";
string html = $@"
    <div>
        <h1>Welcome, {name}!</h1>
    </div>";
36
142