This package delivers a robust C# parsing and expression execution API designed for Unity compatibility across multiple platforms. Implemented in C# with no external dependencies, it maintains broad compatibility with modern Unity versions and .NET frameworks.
This package is available for purchase on the Unity Asset Store. A valid license is required for use in any project. See License.md for details.
Evaluate simple C# expressions immediately:
using GameDevWare.Dynamic.Expressions.CSharp;
var result = CSharpExpression.Evaluate<int>("2 * (2 + 3) << 1");
// Returns 20Parse an expression once and execute it multiple times with high performance:
using GameDevWare.Dynamic.Expressions.CSharp;
using System.Linq.Expressions;
var mathExpr = "Math.Max(x, y)";
var exprTree = CSharpExpression.ParseFunc<double, double, double>(mathExpr, arg1Name: "x", arg2Name: "y");
// Returns Expression<Func<double, double, double>>
var func = exprTree.CompileAot();
// Returns Func<double, double, double>
var result = func(10.5, 20.0); // 20.0- iOS (IL2CPP)
- Android (Mono/IL2CPP)
- WebGL
- Windows / macOS / Linux
- Consoles (Nintendo Switch, PS4/PS5, Xbox)
Important
AOT Platforms (iOS, WebGL, IL2CPP):
Projects targeting AOT compilation require a link.xml file to prevent code stripping. A sample is provided in the package. Refer to Unity's documentation on IL code stripping for more context.
The parser implements C# 4.0+ grammar with support for:
- Operations: Arithmetic, bitwise, logical, Conditional (?:), and Null-coalescing (??).
- Invocations: Methods, delegates, and constructors.
- Access: Properties, fields, and indexers.
- Types: Casting, is/as operators,
typeof(), anddefault(). - Contexts: Checked/Unchecked blocks.
- Advanced: Null-conditional (?. and ?[]), Power operator (
**), and Lambda expressions. - Generics: Generic types and methods, with limited inference of a method's type arguments when they are not given explicitly.
- Extension methods: Extension methods declared by known types, including LINQ queries over sequences.
- Serialization: Pack/Unpack expressions into serializable structures.
For security, the parser restricts type access by default. Additional types must be registered:
// Register specific types
var typeResolver = new KnownTypeResolver(typeof(Mathf), typeof(Time));
CSharpExpression.Evaluate<float>("Mathf.Clamp(Time.time, 0f, 1f)", typeResolver);
// Or allow an entire assembly
var assemblyResolver = new AssemblyTypeResolver(typeof(UnityEngine.Vector3).Assembly);A registered static class also makes its extension methods available on the types they extend. System.Linq.Enumerable is registered by default, so LINQ queries work without any registration:
var items = new[] { 1, 2, 3 };
CSharpExpression.Evaluate<int[], int>("a.Where(x => x > 1).Sum()", items, "a"); // 5An extension method is only considered when the target's own type declares no applicable method, so a declared method always wins. A member of a delegate type is invoked in preference to an extension method of the same name.
A generic method called without explicit type arguments has them inferred from the types of its arguments, including the receiver of an extension method. The result type of a lambda argument is inferred by binding its body, so projections stay strongly typed rather than degrading to object:
var items = new[] { 1, 2, 3 };
// binds Enumerable.Select<int, string>, the expression's type is IEnumerable<string>
CSharpExpression.Evaluate<int[], IEnumerable<string>>("a.Select(x => x.ToString())", items, "a");This is a limited form of inference, enough for Enumerable queries and ordinary extension methods, but not the full C# algorithm:
- A type parameter is fixed by the first argument mentioning it; there is no best common type across several arguments. Given
Pair<T>(T a, T b), the callPair(2.0, 1)bindsTtodouble, whilePair(1, 2.0)fails to bind. - Type parameters are inferred from parameters only. One appearing solely in the return type has to be given explicitly, as it does in C#.
- Constraints are verified after inference rather than used to drive it.
- A lambda argument contributes only once its own parameter types are known, which they are for the
Enumerableoperators.
Explicit type arguments are always honoured and skip inference entirely: a.Where<int>(x => x > 1).
On AOT runtimes an inferred call still instantiates the method at bind time, so see AOT Considerations for the registrations LINQ queries need.
High-performance execution on AOT platforms (iOS, WebGL, Consoles) is achieved via CompileAot():
var expr = (Expression<Func<Vector3>>)(() => new Vector3(1f, 1f, 1f));
var fn = expr.CompileAot(); // Uses AOT-safe execution if needed- Only
Expression<Func<...>>andExpression<Action<...>>are supported. - Register required delegate types:
AotCompilation.RegisterFunc<int, string>();. - Use
AotCompilation.RegisterForFastCall<TTarget, TResult>()for maximum performance on critical paths. - Register the element type of every sequence a LINQ query runs over:
AotCompilation.RegisterLinqFunc<int>();, and useAotCompilation.RegisterLinqFunc<int, string>()when the query projects elements to another type (Select,OrderBy).
- Open Window > Package Manager.
- Select Packages: My Assets.
- Find C# Eval() and click Download/Import.
Install-Package GameDevWare.Dynamic.ExpressionsWe are continuously improving the package. Planned features include:
- Generic type inference
- C# 6.0+ extended syntax support
- Extension method support
Support: support@gamedevware.com
- Feature: Added out/ref parameter write-back for Invoke node and AOT expression executor.
- Fix: Fixed multi-dimensional array packing/unpacking.
- Fix: Correctly format new expression types (Member/List Init) from syntax trees.
- Change: Internal renaming of
AssignmenttoAssignmentBinding(backward compatible).
- Breaking: Distributed as a Unity Package instead of a legacy
.dll. - Feature: Added support for Object and Collection Initializers.
- Chore: Increased C# language support and raised minimum .NET version to 4.6+.
- Feature: Added
globalparameter for context-aware expressions. - Fix: Standardized
arg4Namenaming across methods.