CompiledDelegate
Fast invocation wrappers for compiled delegates. Eliminates the overhead of DynamicInvoke by extracting parameter values and calling the delegate directly. Supports sync, async, single-parameter, and multi-parameter signatures.
internal abstract class CompiledDelegate
internal sealed class CompiledFunc<TParam, TReturn> : CompiledDelegate
internal sealed class CompiledAction<TParam> : CompiledDelegate
internal sealed class CompiledAsyncFunc<TParam, TReturn> : CompiledDelegate
internal sealed class CompiledAsyncAction<TParam> : CompiledDelegate
internal sealed class CompiledMultiParamDelegate : CompiledDelegate
internal sealed class CompiledAsyncMultiParamDelegate : CompiledDelegate
internal static class CompiledDelegateFactory
Namespace: RoslynRules.Models
Key characteristics:
- Abstract base (
CompiledDelegate) with a singleInvoke(object?)method - Typed wrappers for single-parameter delegates avoid
DynamicInvokeoverhead - Async wrappers support both sync-blocking (
Invoke) and true async (InvokeAsync) paths - Multi-parameter delegates fall back to
DynamicInvoke(slower but necessary) CompiledDelegateFactoryuses reflection to auto-detect the correct wrapper type- Marked with
[RequiresUnreferencedCode]— may not work with trimming or AOT
CompiledDelegate (Base)
Methods
| Method | Return | Description |
|---|---|---|
Invoke(object? parameter) | object? | Invokes the delegate with the extracted parameter value |
Typed Single-Parameter Wrappers
CompiledFunc<TParam, TReturn>
Wraps Func<TParam, TReturn>.
var del = new CompiledFunc<string, bool>(s => s.Length > 0);
var result = del.Invoke("hello"); // returns true (as object?)
CompiledAction<TParam>
Wraps Action<TParam> (void return).
var del = new CompiledAction<string>(s => Console.WriteLine(s));
var result = del.Invoke("hello"); // returns null
CompiledAsyncFunc<TParam, TReturn>
Wraps Func<TParam, Task<TReturn>>.
var del = new CompiledAsyncFunc<string, int>(async s =>
{
await Task.Delay(1);
return s.Length;
});
// Sync-blocking (uses ConfigureAwait(false) to avoid deadlock)
var result = del.Invoke("hello");
// True async
var resultAsync = await del.InvokeAsync("hello");
CompiledAsyncAction<TParam>
Wraps Func<TParam, Task> (async void).
var del = new CompiledAsyncAction<string>(async s =>
{
await File.WriteAllTextAsync("log.txt", s);
});
del.Invoke("log entry"); // blocks
await del.InvokeAsync("log entry"); // async
Multi-Parameter Wrappers
CompiledMultiParamDelegate
Wraps any delegate with more than one parameter. Falls back to DynamicInvoke.
Func<int, int, int> add = (a, b) => a + b;
var del = new CompiledMultiParamDelegate(add);
// Parameter is expected to be RuleParameter[]
var result = del.Invoke(new[]
{
new RuleParameter("a", typeof(int), 5),
new RuleParameter("b", typeof(int), 3)
}); // returns 8 (as object?)
CompiledAsyncMultiParamDelegate
Wraps async multi-parameter delegates.
Func<int, int, Task<int>> addAsync = async (a, b) => { await Task.Yield(); return a + b; };
var del = new CompiledAsyncMultiParamDelegate(addAsync);
var result = del.Invoke(new[] { new RuleParameter("a", typeof(int), 5), new RuleParameter("b", typeof(int), 3) });
var resultAsync = await del.InvokeAsync(new[] { ... });
CompiledDelegateFactory
Methods
Wrap(Delegate)
Creates the appropriate CompiledDelegate wrapper from a raw Delegate.
public static CompiledDelegate Wrap(Delegate del)
Detection logic:
- If the delegate has more than 1 parameter →
CompiledMultiParamDelegateorCompiledAsyncMultiParamDelegate - If the return type is
Task→CompiledAsyncAction<TParam> - If the return type is
Task<T>→CompiledAsyncFunc<TParam, T> - If the return type is
void→CompiledAction<TParam> - Otherwise →
CompiledFunc<TParam, TReturn>
Parameters
| Parameter | Type | Description |
|---|---|---|
del | Delegate | The raw compiled delegate to wrap |
Returns
CompiledDelegate — The typed wrapper for fast invocation.
Attributes
[RequiresUnreferencedCode("RoslynRules uses reflection to inspect delegate signatures and instantiate generic wrappers. This code may not work correctly with trimming or AOT.")]
Usage Example
Basic wrapping with the factory
using RoslynRules.Models;
// Simple sync function
Func<string, bool> isValid = s => !string.IsNullOrEmpty(s);
var compiled = CompiledDelegateFactory.Wrap(isValid);
// Fast invoke (no DynamicInvoke)
var result = compiled.Invoke("hello"); // true
// Async function
Func<int, Task<int>> doubleAsync = async x => { await Task.Delay(1); return x * 2; };
var compiledAsync = CompiledDelegateFactory.Wrap(doubleAsync);
// Sync-blocking path
var syncResult = compiledAsync.Invoke(5); // 10
// True async path
var asyncResult = await ((CompiledAsyncFunc<int, int>)compiledAsync).InvokeAsync(5); // 10
Multi-parameter fallback
Func<string, int, bool> checkLength = (s, max) => s.Length <= max;
var compiled = CompiledDelegateFactory.Wrap(checkLength);
// Must use RuleParameter[] for multi-parameter delegates
var result = compiled.Invoke(new[]
{
new RuleParameter("s", typeof(string), "hello"),
new RuleParameter("max", typeof(int), 10)
}); // true
Integration with Rule execution
public class RuleExecutor
{
private readonly CompiledDelegate _compiledExpression;
public RuleExecutor(Rule rule, ExpressionCompiler compiler)
{
var rawDelegate = compiler.Compile(rule.Expression, rule.Parameters);
_compiledExpression = CompiledDelegateFactory.Wrap(rawDelegate);
}
public bool Evaluate(object parameter)
{
var result = _compiledExpression.Invoke(parameter);
return (bool)result!;
}
}
Performance Notes
| Delegate Type | Wrapper | Invoke Overhead |
|---|---|---|
Func<T, R> | CompiledFunc<T, R> | Direct cast + call (fast) |
Action<T> | CompiledAction<T> | Direct cast + call (fast) |
Func<T, Task<R>> | CompiledAsyncFunc<T, R> | Direct cast + block/await |
Func<T1, T2, R> | CompiledMultiParamDelegate | DynamicInvoke (slower) |
Func<T1, T2, Task<R>> | CompiledAsyncMultiParamDelegate | DynamicInvoke + block/await |
For best performance, prefer single-parameter expressions in rules. Multi-parameter support exists for flexibility but incurs reflection overhead.
Related
- ExpressionCompiler — Compiles C# expressions to delegates
- Delegate Types — Supported expression signatures
- Rule — Uses
CompiledDelegateinternally for expression evaluation - AOT Compatibility — Notes on trimming and AOT limitations