Linq.Expression.Optimizer
1.0.42
dotnet add package Linq.Expression.Optimizer --version 1.0.42
NuGet\Install-Package Linq.Expression.Optimizer -Version 1.0.42
<PackageReference Include="Linq.Expression.Optimizer" Version="1.0.42" />
<PackageVersion Include="Linq.Expression.Optimizer" Version="1.0.42" />
<PackageReference Include="Linq.Expression.Optimizer" />
paket add Linq.Expression.Optimizer --version 1.0.42
#r "nuget: Linq.Expression.Optimizer, 1.0.42"
#:package Linq.Expression.Optimizer@1.0.42
#addin nuget:?package=Linq.Expression.Optimizer&version=1.0.42
#tool nuget:?package=Linq.Expression.Optimizer&version=1.0.42
Linq.Expression.Optimizer
Lightweight optimizer of System.Linq.Expression expressions. Just basic boolean algebra and reductions, constant and tuple/anonymous type eliminations. For side-effect-free Expressions. No compilation-subjective optimizations. This is meant to be used with expressions that are not compiled but transferred to other domains.
Supported frameworks: Net 3.5, Net 4.5-... (and Mono), .NET Standard 1.6 (so also .NET Core)
Supported optimizations
Example as a quote. There are various other cases also.
- Replace constants comparisons:
3 < 4 -> true - Remove anonymous types:
new AnonymousObject(Item1 = x, Item2 = "").Item1 --> x - Cut not used condition:
if false then x else y -> y - Remove not:
not(false) -> true - Binary tree balancing:
a or (b or (c or (d or (e or (f or (g or h)))))) -> ((a or b) or (c or d)) or ((e or f) or (g or h)) - Captured closure constant ("free variable") evaluation:
y = 3 and (y + x) -> (3 + x) - Execute simple math:
5 * 3 -> 15 - Boolean algebra reductions:
- gather
(x or y) and (x or z) -> x or (y and z) - identity
false or y -> y - annihilate
true or x -> true - absorb
x and (x or y) -> x - idempotence
y or y -> y - complement
x and not(x) -> false - doubleNegation
not(not(y)) -> y - deMorgan
not(x) and not(y) -> not(x or y)
- gather
What a rewrite keeps
The optimized expression gives the value of the original for every input and throws only where the original throws. Optimizing a valid tree does not throw.
- Evaluation order. A rewrite may evaluate fewer operands, but never one the original would have
skipped, and the operands that stay keep their left-to-right order: a guard stays ahead of what it
guards (
o != null and o.Flag). An exception the original would throw may disappear with a subtree that is optimized away. - The same operand. Two operands are one only when they are the same parameter (by reference, not by name), constants holding the same reference (the same value for value types and strings), or the same member chain over those. Anything holding a method call, an Invoke, a New or an index is never the same as another.
- Arithmetic. Constants are regrouped (
(x + 2) + 3 -> x + 5) for integral types in unchecked nodes only. Identities are applied where they are exact: notx + 0.0(it turns -0.0 into 0.0), notx * 0orx % 1on a Nullable. A decimal is the same by value: its constants are folded and its identities applied, and a result may carry another scale than the original's (6.0for6.00). A fold that overflows is left for the run time. - Operators. A node is folded or rewritten only when its operator is the built-in one: no
Method; anop_*method of string, decimal, DateTime, DateTimeOffset, TimeSpan, Guid or BigInteger (also the staticString.ConcatandString.Equals); or an F# operator (Operators.op_*,LanguagePrimitives.*Dynamic) over those types, primitives, enums, and options, lists, tuples and Nullables of them. Objects are compared by reference, never by value. - Comparison ranges (
x > 1 and x > 2 -> x > 2) are left alone over Nullable operands and NaN constants. - Tuples and anonymous types. A tuple is a System.Tuple, a System.ValueTuple or an AnonymousObject;
a member of an anonymous type is found through
NewExpression.Members. - Other nodes. A node type without a rule (Default, Index, Switch, Try, ...) stays as it is.
- Deep trees. A tree nested deeper than the stack has room for is optimized down to that depth and left as it is below, instead of overflowing the stack.
- Captured values. A captured closure constant is read when the expression is optimized: the optimized expression does not see a later change of the captured variable.
Background
This is a side-track from SQLProvider, excellent tool that is kind of OR-mapper with auto-generated objects, so it compiles any databases to .NET-language, and works fast on design time in Visual Studio or other editors.
But I needed better SQL-queries. So this optimises .NET LINQ-expressions. These expressions were not meant to be compiled, so Nessos LinqOptimizer was not the right tool. I thought that .NET would optimize these automatically, but no.
Read the Getting started tutorial to learn more.
Documentation: http://thorium.github.io/Linq.Expression.Optimizer
Tests
tests/Linq.Expression.Optimizer.Tests is the example-based suite over LINQ queries.
tests/Linq.Expression.Optimizer.PropertyTests is an FsCheck suite over random expression
trees (booleans, integers, doubles with NaN, decimals, nullable integers, strings and an object
that may be null over fifteen parameters, two of them named alike and two unnamed, in the shapes
every reduction rule is written for): the optimized tree
compiles to the same function as the original on a grid of inputs, optimizing never throws,
optimizing twice is optimizing once, the tree never grows, and every reduction rule fires
on some generated tree. An exception the original would throw (x / 0, null.Length) may
disappear when the subtree that threw it is optimized away - the library is for
side-effect-free expressions - but a value never changes and no new exception appears.
Maintainer(s)
If you want more optimizations, please feel free to send PRs!
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETFramework 4.8.1
- FSharp.Core (>= 8.0.301)
-
.NETStandard 2.0
- FSharp.Core (>= 8.0.301)
-
.NETStandard 2.1
- FSharp.Core (>= 8.0.301)
-
net10.0
- FSharp.Core (>= 8.0.301)
-
net8.0
- FSharp.Core (>= 8.0.301)
NuGet packages (4)
Showing the top 4 NuGet packages that depend on Linq.Expression.Optimizer:
| Package | Downloads |
|---|---|
|
Dosaic.Plugins.Persistence.EfCore.Abstractions
A plugin-first dotnet framework for rapidly building anything hosted in the web. |
|
|
Dosaic.Plugins.Persistence.EfCore.NpgSql
A plugin-first dotnet framework for rapidly building anything hosted in the web. |
|
|
SpeedyCourse.Entities
Package Description |
|
|
XSpecification.Linq
XSpecification is an implementation of Specification pattern for Linq and Elasticsearch |
GitHub repositories (3)
Showing the top 3 popular GitHub repositories that depend on Linq.Expression.Optimizer:
| Repository | Stars |
|---|---|
|
rnwood/smtp4dev
smtp4dev - the fake smtp email server for development and testing
|
|
|
scottksmith95/LINQKit
LINQKit is a free set of extensions for LINQ to SQL and Entity Framework power users.
|
|
|
zzzprojects/System.Linq.Dynamic.Core
The .NET Standard / .NET Core version from the System Linq Dynamic functionality.
|
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.42 | 398 | 11/11/2026 |
| 1.0.41 | 141 | 11/11/2026 |
| 1.0.39 | 22,356 | 6/11/2026 |
| 1.0.38 | 215 | 6/9/2026 |
| 1.0.37 | 496 | 6/8/2026 |
| 1.0.36 | 1,223 | 5/26/2026 |
| 1.0.35 | 6,731 | 4/8/2026 |
| 1.0.34 | 72,300 | 11/9/2025 |
| 1.0.33 | 3,140 | 9/8/2025 |
| 1.0.32 | 2,427 | 6/19/2025 |
| 1.0.31 | 802 | 6/4/2025 |
| 1.0.29 | 15,798 | 3/31/2025 |
| 1.0.28 | 1,771 | 3/26/2025 |
| 1.0.27 | 1,093 | 3/18/2025 |
| 1.0.24 | 12,620 | 11/7/2024 |
| 1.0.23 | 47,182 | 6/26/2024 |
| 1.0.22 | 671 | 6/26/2024 |
DateOnly and TimeOnly operators are built-in again: two constants fold, and the range and equality rules apply
Two Version constants fold again
c ? y : y keeps both branches for 0.0 and -0.0, a DateTimeOffset in two offsets and a DateTime of two kinds