C#
Expression Trees
String.Contains
Programming
.NET

How do I create an expression tree to represent 'String.Containsterm' in C?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In C#, an expression tree for x => x.Contains(term) is built by combining a parameter expression, a constant expression for the search term, and a method-call expression for string.Contains. This is common in LINQ providers, search builders, and dynamic filtering code.

The important distinction is that an expression tree describes code; it does not immediately execute it. That makes it useful when you want to pass the logic to Entity Framework, LINQ to Objects, or another component that can inspect the expression structure.

Build s => s.Contains(term)

Here is the simplest expression tree for a plain string parameter:

csharp
1using System;
2using System.Linq.Expressions;
3
4string term = "cat";
5
6ParameterExpression parameter = Expression.Parameter(typeof(string), "s");
7ConstantExpression constant = Expression.Constant(term, typeof(string));
8
9var containsMethod = typeof(string).GetMethod(nameof(string.Contains), new[] { typeof(string) })!;
10MethodCallExpression body = Expression.Call(parameter, containsMethod, constant);
11
12Expression<Func<string, bool>> predicate =
13    Expression.Lambda<Func<string, bool>>(body, parameter);
14
15Console.WriteLine(predicate);
16Console.WriteLine(predicate.Compile()("concatenate"));

This prints an expression similar to:

text
s => s.Contains("cat")
True

The key step is Expression.Call, which represents the method invocation in the tree.

Build x => x.Name.Contains(term)

In real applications, you often want to call Contains on a property rather than on the input object directly.

csharp
1using System;
2using System.Linq.Expressions;
3
4public class Product
5{
6    public string Name { get; set; } = "";
7}
8
9string term = "book";
10
11ParameterExpression parameter = Expression.Parameter(typeof(Product), "p");
12MemberExpression property = Expression.Property(parameter, nameof(Product.Name));
13ConstantExpression constant = Expression.Constant(term);
14
15var containsMethod = typeof(string).GetMethod(nameof(string.Contains), new[] { typeof(string) })!;
16MethodCallExpression body = Expression.Call(property, containsMethod, constant);
17
18Expression<Func<Product, bool>> predicate =
19    Expression.Lambda<Func<Product, bool>>(body, parameter);
20
21Console.WriteLine(predicate);

This expression represents:

csharp
p => p.Name.Contains("book")

That shape is exactly what many LINQ APIs expect.

Add a Null Check

string.Contains throws if the target string is null, so a dynamic expression often needs a null guard:

csharp
1using System;
2using System.Linq.Expressions;
3
4public class Product
5{
6    public string? Name { get; set; }
7}
8
9string term = "book";
10
11ParameterExpression parameter = Expression.Parameter(typeof(Product), "p");
12MemberExpression property = Expression.Property(parameter, nameof(Product.Name));
13ConstantExpression constant = Expression.Constant(term);
14ConstantExpression nullString = Expression.Constant(null, typeof(string));
15
16var containsMethod = typeof(string).GetMethod(nameof(string.Contains), new[] { typeof(string) })!;
17
18BinaryExpression notNull = Expression.NotEqual(property, nullString);
19MethodCallExpression containsCall = Expression.Call(property, containsMethod, constant);
20BinaryExpression body = Expression.AndAlso(notNull, containsCall);
21
22Expression<Func<Product, bool>> predicate =
23    Expression.Lambda<Func<Product, bool>>(body, parameter);

Now the generated logic is effectively:

csharp
p => p.Name != null && p.Name.Contains("book")

That version is safer for in-memory evaluation and clearer for translators.

Why Use an Expression Tree Instead of a Delegate

This is an ordinary delegate:

csharp
Func<Product, bool> compiled = p => p.Name.Contains("book");

That can execute, but it cannot be inspected structurally by a query provider. An Expression<Func<Product, bool>> preserves the shape of the code so a framework can translate it, for example into SQL.

If your goal is dynamic query construction, expression trees are the right abstraction. If your goal is only in-memory execution, a plain delegate may be simpler.

Reusable Helper Method

Here is a small helper for building a property-contains predicate:

csharp
1using System;
2using System.Linq.Expressions;
3
4public static Expression<Func<T, bool>> BuildContains<T>(string propertyName, string term)
5{
6    ParameterExpression parameter = Expression.Parameter(typeof(T), "x");
7    MemberExpression property = Expression.Property(parameter, propertyName);
8    ConstantExpression constant = Expression.Constant(term);
9
10    var containsMethod = typeof(string).GetMethod(nameof(string.Contains), new[] { typeof(string) })!;
11    MethodCallExpression body = Expression.Call(property, containsMethod, constant);
12
13    return Expression.Lambda<Func<T, bool>>(body, parameter);
14}

This is useful for dynamic search UIs, though production code should add validation to ensure the named property exists and is actually a string.

Common Pitfalls

The biggest pitfall is confusing a compiled delegate with an expression tree. Func<T, bool> executes code, while Expression<Func<T, bool>> represents code.

Another mistake is calling the wrong overload of string.Contains. If you build the method metadata with reflection, make sure you request the exact signature you want.

Null handling is another frequent issue. If the target property can be null, the generated expression should guard against that unless the query provider handles it specially.

Finally, dynamic property access needs validation. If you build an expression from a string property name and that property is missing or not a string, the code will fail at runtime.

Summary

  • Use Expression.Parameter, Expression.Constant, and Expression.Call to build Contains expressions.
  • Wrap the call in Expression.Lambda<Func<...>> to get a typed predicate tree.
  • Use a member expression when the target is a string property such as p.Name.
  • Add a null check when the target string can be missing.
  • Use expression trees for dynamic query construction, not just ordinary in-memory filtering.
  • Be careful to select the correct string.Contains overload when using reflection.

Course illustration
Course illustration

All Rights Reserved.