Read or download the complete SDK project, then follow its setup on your computer. The separate C# companion practice below runs in SharpRune’s .NET 9 console environment.
The runtime contract
An expression tree is an object representation of code structure, while a delegate is directly callable behavior. Both can describe the same arithmetic without being the same kind of value. The tree exposes an Add node with a Multiply node on its left, and the program then requests real interpreted execution through Compile with preferInterpretation true. That request avoids claiming runtime native-code generation in the browser. The ordinary delegate supplies an independent callable comparison. The example does not send a query to a provider or assume that arbitrary C# constructs can be represented or translated as expression nodes.
Trace the portable program
The lambda multiplies its input by two and adds one. Inspecting the tree reports node:Add and left:Multiply. Interpreting it for input three produces seven, and invoking the directly declared delegate for three also produces seven. The final lines are interpreted:7 and delegate:7. Those equal values establish the bounded arithmetic result, not identical allocation, compilation, or execution cost. Tree inspection remains possible before execution, which is useful when designing translators or validators; whether a particular external provider supports these nodes is a separate contract requiring that provider's real behavior.
Execute the actual desktop lab
The desktop project exercises the same public expression API, including the preference for interpretation, instead of claiming a tree is executable merely because it prints a familiar lambda. A tree can also be built manually from parameters, constants, and operation nodes, which is useful when the structure itself is the product. A translator may accept only a documented subset of those shapes. A directly compiled C# delegate has no general public facility that reconstructs the original source tree from its behavior. Choose the representation deliberately: an inspectable computation description and an opaque callable function solve different boundary problems even when this small example returns the same integer.
Pitfalls and boundary limits
Not every C# feature can appear in an expression tree produced by lambda conversion, and not every representable node is supported by every remote provider. Compile with an interpretation preference is a local execution request, not a request to translate SQL or prove NativeAOT publication. Execution also has a lifetime: captured objects can remain reachable through the tree or resulting delegate. These issues are independent of the arithmetic answer. The sample uses a parameterized value-only expression and small integers to avoid hidden state and overflow, so its output does not accidentally depend on provider behavior or a mutable ambient object.
Practice one: isolate behavior
First change the final constant from one to three in both computations and predict nine for input three. Inspect the root and left node kinds again and explain why those structural kinds remain Add and Multiply. Hint: a literal value can change without changing the shape of the operation tree. Then replace addition with multiplication and predict both the new result and the root node kind before running. Preserve a direct expected number instead of accepting equality between two implementations that could share the same mistaken formula.
Practice two: design and verify
For another exercise, construct the same parameterized tree manually and compare its result for zero, a negative input, and three. Hint: reuse the same ParameterExpression wherever the logical parameter is intended; unrelated parameter objects are not interchangeable just because their display names match. Add a validator that accepts only the specific arithmetic node kinds you support and rejects an unsupported shape explicitly. Keep local interpretation tests separate from any later provider integration. A validator that recognizes a shape does not prove an untested database or native deployment can execute it.
Review the evidence
Review expression use by identifying whether consumers need structure, execution, translation, or serialization, and then state which node subset and lifetime rules apply. The four baseline lines establish the actual shape and bounded local result. The desktop run confirms the public API behavior in another runtime environment, without giving a performance ranking between interpretation and direct delegates. If performance becomes relevant, measure the real workload and include tree construction and compilation costs in the intended scope. Do not infer native code generation merely from the word Compile or infer universal provider support from a successful local arithmetic calculation.
Expression trees are data and delegates execute — desktop runtime lab
Set up the SDK project
- Save both files at their exact paths in a fresh owned directory.
- Use a .NET 10 SDK, then run dotnet run --project RuntimeLab.csproj. This project intentionally uses C# 13 syntax.
- Workers and waits are bounded; a timeout or a violated invariant fails the process. No benchmark or NativeAOT publication is implied by an ordinary run.
Download complete SDK project ↗
Project files
Program.cs
Download file ↗using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Reflection;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
Expression<Func<int, int>> tree = value => value * 2 + 1;
Func<int, int> direct = value => value * 2 + 1;
var body = (BinaryExpression)tree.Body;
Console.WriteLine($"node:{body.NodeType}");
Console.WriteLine($"left:{body.Left.NodeType}");
Console.WriteLine($"interpreted:{tree.Compile(preferInterpretation: true)(3)}");
Console.WriteLine($"delegate:{direct(3)}");
RuntimeLab.csproj
Download file ↗<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>13.0</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>false</PublishAot>
</PropertyGroup>
</Project>
Expected result
Exact behavioral stdout on the stated desktop lab: node:Add left:Multiply interpreted:7 delegate:7 Variable diagnostics, where emitted, go to stderr and must not be treated as portable timing or allocation guarantees.
Use the listed SDK and dependencies for this project. Open C# practice runs the separate portable exercise.
C# companion practice
using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Reflection;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
Expression<Func<int, int>> tree = value => value * 2 + 1;
Func<int, int> direct = value => value * 2 + 1;
var body = (BinaryExpression)tree.Body;
Console.WriteLine($"node:{body.NodeType}");
Console.WriteLine($"left:{body.Left.NodeType}");
Console.WriteLine($"interpreted:{tree.Compile(preferInterpretation: true)(3)}");
Console.WriteLine($"delegate:{direct(3)}");
C# practice output
node:Add left:Multiply interpreted:7 delegate:7
A tree can be inspected before it is executed. preferInterpretation requests an execution strategy; it does not show a database query or prove generated native code exists.