DollarSignEngine 1.3.1

dotnet add package DollarSignEngine --version 1.3.1
                    
NuGet\Install-Package DollarSignEngine -Version 1.3.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DollarSignEngine" Version="1.3.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DollarSignEngine" Version="1.3.1" />
                    
Directory.Packages.props
<PackageReference Include="DollarSignEngine" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DollarSignEngine --version 1.3.1
                    
#r "nuget: DollarSignEngine, 1.3.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#addin nuget:?package=DollarSignEngine&version=1.3.1
                    
Install DollarSignEngine as a Cake Addin
#tool nuget:?package=DollarSignEngine&version=1.3.1
                    
Install DollarSignEngine as a Cake Tool

DollarSignEngine

Dynamically evaluate and interpolate C# expressions at runtime with ease, leveraging the power of the Roslyn compiler.

Introduction

The DollarSignEngine is a robust C# library designed to simplify the process of dynamically evaluating and interpolating expressions at runtime. Ideal for applications requiring on-the-fly evaluation of string templates, it offers developers the flexibility to inject variables and execute complex C# expressions with the same syntax as C# string interpolation.

Guiding Principles

  1. Core Purpose: Enable runtime evaluation of C# string interpolation ($"{}") exactly as it works in compile-time C#.

  2. Extension Rules:

    • All features natively supported in C# string interpolation must be maintained
    • Avoid extending functionality beyond standard C# interpolation syntax
    • Only add features essential for runtime evaluation

Features

  • Dynamic Expression Evaluation: Evaluate C# expressions dynamically at runtime, with support for interpolation and complex logic.
  • Flexible Parameter Injection: Easily pass parameters into expressions using dictionaries, anonymous objects, or regular C# objects.
  • Method & LINQ Support: Call methods on objects and use LINQ expressions within interpolated strings.
  • Format Specifiers & Alignment: Full support for C# format specifiers and alignment in interpolated expressions.
  • Custom Variable Resolution: Provide custom variable resolvers for advanced use cases.
  • Multiple Syntax Options: Support for both standard C# interpolation {expression} and dollar-sign ${expression} syntax.
  • Comprehensive Error Handling: Provides detailed exceptions for compilation and runtime errors to ease debugging.
  • Expression Caching: Compiled expressions are cached for improved performance with repeated evaluations.

Installation

The library is available on NuGet. You can install it using the following command:

dotnet add package DollarSignEngine

Usage

Below are some examples of how to use the DollarSignEngine to evaluate expressions dynamically.

Basic Interpolation

// Simple interpolation with current date
var result = await DollarSign.EvalAsync("Today is {DateTime.Now:yyyy-MM-dd}");
Console.WriteLine(result); // Outputs: Today is 2023-05-01 (based on current date)

// With parameters using anonymous object
var name = "John";
var result = await DollarSign.EvalAsync("Hello, {name}!", new { name });
Console.WriteLine(result); // Outputs: Hello, John!

// With parameters using dictionary
var parameters = new Dictionary<string, object?> { { "name", "John" } };
var result = await DollarSign.EvalAsync("Hello, {name}!", parameters);
Console.WriteLine(result); // Outputs: Hello, John!

Using Custom Objects

// Using a class directly
public class User
{
    public string Username { get; set; } = string.Empty;
    public int Age { get; set; }
}

var user = new User { Username = "Alice", Age = 30 };
var result = await DollarSign.EvalAsync("User: {Username}, Age: {Age}", user);
Console.WriteLine(result); // Outputs: User: Alice, Age: 30

// Using anonymous types with nested properties
var person = new { 
    Name = "Jane", 
    Address = new { City = "New York", Country = "USA" } 
};
var result = await DollarSign.EvalAsync("Person: {Name} from {Address.City}", person);
Console.WriteLine(result); // Outputs: Person: Jane from New York

// Calling a method on a custom object
public class Greeter
{
    public string Name { get; set; } = string.Empty;

    public string Hello()
    {
        return $"hello, {Name}";
    }
}

var greeter = new Greeter { Name = "Bob" };
var options = new DollarSignOptions { SupportDollarSignSyntax = true };
var result = await DollarSign.EvalAsync("Greeting: ${Hello()}", greeter, options);
Console.WriteLine(result); // Outputs: Greeting: hello, Bob

// Without dollar sign syntax, the expression is treated as literal
var standardResult = await DollarSign.EvalAsync("Greeting: ${Hello()}", greeter);
Console.WriteLine(standardResult); // Outputs: Greeting: ${Hello()}

Method Calls and LINQ Expressions

// Calling methods on strings
var text = "hello world";
var result = await DollarSign.EvalAsync("Uppercase: {text.ToUpper()}", new { text });
Console.WriteLine(result); // Outputs: Uppercase: HELLO WORLD

// Using LINQ with collections
var numbers = new List<int> { 1, 2, 3, 4, 5 };
var result = await DollarSign.EvalAsync("Even numbers: {numbers.Where(n => n % 2 == 0).Count()}", new { numbers });
Console.WriteLine(result); // Outputs: Even numbers: 2

// Chaining method calls
var data = "  sample text  ";
var result = await DollarSign.EvalAsync("Processed: {data.Trim().ToUpper().Replace('A', 'X')}", new { data });
Console.WriteLine(result); // Outputs: Processed: SXMPLE TEXT

// Using strongly-typed custom objects for method calls
public class Calculator
{
    public int Add(int a, int b) => a + b;
    public int Multiply(int a, int b) => a * b;
}

var calc = new Calculator();
var result = await DollarSign.EvalAsync("Sum: {calc.Add(5, 3)}, Product: {calc.Multiply(5, 3)}", new { calc });
Console.WriteLine(result); // Outputs: Sum: 8, Product: 15

Important Note: For method calls and LINQ expressions to work properly, you need to pass strongly-typed objects rather than anonymous types. This is because the engine needs access to the type information at runtime to properly compile and execute the methods.

Format Specifiers and Alignment

// Using format specifiers
var price = 123.456;
var result = await DollarSign.EvalAsync("Price: {price:C2}", new { price });
Console.WriteLine(result); // Outputs: Price: $123.46

// Using alignment
var number = 42;
var result = await DollarSign.EvalAsync("Left aligned: {number,-10} | Right aligned: {number,10}", new { number });
Console.WriteLine(result); // Outputs: Left aligned: 42         | Right aligned:         42

// Combined alignment and format
var percentage = 0.8654;
var result = await DollarSign.EvalAsync("Progress: {percentage,8:P1}", new { percentage });
Console.WriteLine(result); // Outputs: Progress:    86.5%

Conditional Logic

// Simple ternary operation
var age = 20;
var result = await DollarSign.EvalAsync("You are {(age >= 18 ? \"adult\" : \"minor\")}.", new { age });
Console.WriteLine(result); // Outputs: You are adult.

// Nested ternary operations
var score = 85;
var result = await DollarSign.EvalAsync("Grade: {(score >= 90 ? \"A\" : score >= 80 ? \"B\" : \"C\")}.", new { score });
Console.WriteLine(result); // Outputs: Grade: B.

// Complex condition with formatting
var price = 123.456;
var discount = true;
var result = await DollarSign.EvalAsync("Final price: {(discount ? price * 0.9 : price):C2}", 
    new { price, discount });
Console.WriteLine(result); // Outputs: Final price: $111.11

Working with Collections

var numbers = new List<int> { 1, 2, 3, 4, 5 };
var result = await DollarSign.EvalAsync("Total sum: {numbers.Sum()}. Items count: {numbers.Count}", new { numbers });
Console.WriteLine(result); // Outputs: Total sum: 15. Items count: 5

var settings = new Dictionary<string, string> { { "Theme", "Dark" }, { "FontSize", "12" } };
var result = await DollarSign.EvalAsync("Theme: {settings[\"Theme\"]}, Font Size: {settings[\"FontSize\"]}", new { settings });
Console.WriteLine(result); // Outputs: Theme: Dark, Font Size: 12

Dollar Sign Syntax

// When working with text that contains literal curly braces (like JSON),
// enable dollar sign syntax to specify which parts should be interpolated
var options = new DollarSignOptions { SupportDollarSignSyntax = true };

var user = new { name = "Alice", age = 30 };

// With dollar sign syntax enabled, only ${...} is interpolated
var jsonTemplate = "{ \"user\": { \"name\": \"{name}\", \"age\": ${age} } }";
var result = await DollarSign.EvalAsync(jsonTemplate, user, options);
Console.WriteLine(result); 
// Outputs: { "user": { "name": "{name}", "age": 30 } }

// In standard mode (default), all {...} expressions are interpolated
var standardResult = await DollarSign.EvalAsync("{ \"user\": { \"name\": \"{name}\", \"age\": {age} } }", user);
Console.WriteLine(standardResult); 
// Outputs: { "user": { "name": "Alice", "age": 30 } }

Method Calls and LINQ: Requirements and Limitations

When using method calls and LINQ expressions within interpolated strings, there are a few important considerations:

Requirements

  1. Strongly-Typed Objects: For method calls to work properly, pass strongly-typed objects rather than anonymous types whenever possible. The engine needs access to type information at runtime.

    // This works well - using a concrete class
    var calculator = new Calculator();
    var result = await DollarSign.EvalAsync("Result: {calculator.Add(5, 3)}", new { calculator });
    
    // May have limitations with anonymous types
    var data = new { calc = (Func<int, int, int>)((a, b) => a + b) };
    
  2. Required References: The engine automatically adds references to common assemblies like System.Linq, but custom assemblies might need to be explicitly included via reflection.

  3. Type Compatibility: Parameters in method calls must be compatible with expected parameter types. The engine attempts to convert types when possible.

Limitations

  1. Anonymous Method Limitations: Methods defined inline as lambda expressions in anonymous objects may have restricted functionality compared to methods in concrete classes.

  2. Extension Method Support: Extension methods (like many LINQ methods) require the appropriate namespace to be in scope. The engine includes common namespaces by default.

  3. Performance Considerations: Complex method calls and LINQ expressions require more compilation resources than simple property access.

Configuration Options

The DollarSignOptions class provides several options to customize the behavior of the expression evaluation:

var options = new DollarSignOptions
{
    // Whether to cache compiled expressions. Defaults to true.
    UseCache = true,
    
    // Whether to throw exceptions on errors during evaluation. Defaults to false.
    ThrowOnError = false,
    
    // Custom variable resolver function.
    VariableResolver = variableName => /* Return value for variable */,
    
    // Custom error handler function.
    ErrorHandler = (expression, exception) => /* Return replacement text for errors */,
    
    // The culture to use for formatting. If null, the current culture is used.
    CultureInfo = new CultureInfo("en-US"),
    
    // Whether to support dollar sign syntax in templates.
    // When enabled, {expression} is treated as literal text and ${expression} is evaluated.
    // Defaults to false.
    SupportDollarSignSyntax = true
};

You can also use the static helper methods to create options:

// Default options
var options = DollarSignOptions.Default;

// Options with a specific variable resolver
var resolverOptions = DollarSignOptions.WithResolver(name => /* Resolve variable */);

// Options with error handling
var errorOptions = DollarSignOptions.WithErrorHandler((expr, ex) => /* Handle error */);

// Options that throw exceptions on errors
var throwingOptions = DollarSignOptions.Throwing();

// Options with dollar sign syntax support
var dollarOptions = DollarSignOptions.WithDollarSignSyntax();

// Options with a specific culture
var cultureOptions = DollarSignOptions.WithCulture(new CultureInfo("fr-FR"));

Error Handling

The library provides exceptions for handling errors during expression evaluation:

try
{
    var result = await DollarSign.EvalAsync("{nonExistentVariable + 1}", null, 
        new DollarSignOptions { ThrowOnError = true });
}
catch (DollarSignEngineException ex)
{
    Console.WriteLine($"General error: {ex.Message}");
    // Handle the error...
}
catch (CompilationException ex)
{
    Console.WriteLine($"Compilation error: {ex.Message}");
    Console.WriteLine($"Error details: {ex.ErrorDetails}");
    // Handle compilation error...
}

Synchronous API

The library also provides synchronous versions of the evaluation methods:

// Using object variables
var result = DollarSign.Eval("Hello, {name}!", new { name = "World" });

// Using dictionary variables
var parameters = new Dictionary<string, object?> { { "value", 42 } };
var result = DollarSign.Eval("The answer is {value}.", parameters);

Performance Considerations

  • Compiled expressions are cached to improve performance for repeated calls with UseCache = true (default)
  • Use the ClearCache() method to free memory if needed:
    DollarSign.ClearCache();
    
  • For templates that are evaluated many times with different parameters, consider reusing the same template string
    • Method calls and LINQ queries are more resource-intensive to compile than simple property access
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.3.1 51 5/24/2025
1.3.0 126 5/22/2025
1.2.0 214 5/15/2025
1.1.0 137 5/1/2025
1.0.3 136 6/4/2024
1.0.2 159 4/4/2024
1.0.1 116 4/4/2024