Approaches to handling simple expressions in C#
In this post we'll discuss a few ways to handle dynamic expressions with C#. We'll look at LINQ expression trees and .NET 5 code generation.
Timur Khadimullin
Every now and then we get asked if there’s an easy way to parse user input into filter conditions. Say, for example, we have a viewmodel of type DataThing:
public class DataThing
{
public string Name;
public float Value;
public int Count;
}
From here we’d like to check if a given property of this class satisfies a certain condition. For example, we’ll look at “Value is greater than 15”. But of course, we’d like to be flexible.
The issue
The main issue here is we don’t know the type of property beforehand, so we can’t use generics even if we try to be smart:
public class DataThing
{
public string Name;
public float Value;
public int Count;
}
public static void Main()
{
var data = new DataThing() {Value=10, Name="test", Count = 1};
var values = new List {
new ValueGetter(x => x.Value),
new ValueGetter(x => x.Name)
};
(values[0].Run(data) > 15).Dump();
}
public abstract class ValueGetter
{
public abstract T Run<T>(DataThing d);
}
public class ValueGetter<T> : ValueGetter
{
public Func<DataThing, T> TestFunc;
public ValueGetter(Func<DataThing, T> blah)
{
TestFunc = blah;
}
public override T Run(DataThing d) => TestFunc.Invoke(d); // CS0029 Cannot implicitly convert type…
}
Even if we figured it out it’s obviously way too dependent on DataThing layout to be used everywhere.
LINQ Expression trees
One way to solve this issue is with the help of LINQ expression trees. This way we wrap everything into one delegate with predictable signature and figure out types at runtime:
bool BuildComparer(DataThing data, string field, string op, T value) {
var p1 = Expression.Parameter(typeof(DataThing));
var p2 = Expression.Parameter(typeof(T));
if (op == ">")
{
var expr = Expression.Lambda>(
Expression.MakeBinary(ExpressionType.GreaterThan
, Expression.PropertyOrField(p1, field)
, Expression.Convert(p2, typeof(T))), p1, p2);
var f = expr.Compile();
return f(data, value);
<span style="background-color: inherit; font-family: monospace; font-size: inherit;">} </span>
<span style="background-color: inherit; font-family: monospace; font-size: inherit;">return false;</span>
}
Code DOM CSharpScript
Another way to approach the same problem is to generate C# code that we can compile and run. We’d need Microsoft.CodeAnalysis.CSharp.Scripting package for this to work:
bool BuildScript(DataThing data, string field, string op, T value)
{
var code = $"return {field} {op} {value};";
var script = CSharpScript.Create(code, globalsType: typeof(DataThing), options: ScriptOptions.Default);
var scriptRunner = script.CreateDelegate();
return scriptRunner(data).Result;
}
.NET 5 Code Generator
This is a new .NET 5 feature, that allows us to plug into compilation process and generate classes as we see fit. For example, we’d generate extension methods that would all return correct values from DataThing:
[Generator] // see https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md for even more cool stuff
class AccessorGenerator: ISourceGenerator {
public void Execute(GeneratorExecutionContext context) {
var syntaxReceiver = (CustomSyntaxReceiver) context.SyntaxReceiver;
ClassDeclarationSyntax userClass = syntaxReceiver.ClassToAugment;
SourceText sourceText = SourceText.From($ @ "
public static class DataThingExtensions {
{
// This is where we'd reflect over type members and generate code dynamically. Following code is oversimplification
public static string GetValue<string>(this DataThing d) => d.Name;
public static string GetValue<float>(this DataThing d) => d.Value;
public static string GetValue<int>(this DataThing d) => d.Count;
}
}
", Encoding.UTF8);
context.AddSource("DataThingExtensions.cs", sourceText);
}
public void Initialize(GeneratorInitializationContext context) {
context.RegisterForSyntaxNotifications(() => new CustomSyntaxReceiver());
}
class CustomSyntaxReceiver: ISyntaxReceiver {
public ClassDeclarationSyntax ClassToAugment {
get;
private set;
}
public void OnVisitSyntaxNode(SyntaxNode syntaxNode) {
// Business logic to decide what we're interested in goes here
if (syntaxNode is ClassDeclarationSyntax cds &&
cds.Identifier.ValueText == "DataThing") {
ClassToAugment = cds;
}
}
}
}
Upvote
Timur Khadimullin
Coming from many years of software engineering background I help fellow software developers overcome technical challenges. In spare time I love good puzzles, helping people and enjoying fine wine (ideally, all at the same time).

Related Articles