This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
ExpressiveSharp is a Roslyn source generator that enables modern C# syntax (null-conditional ?., switch expressions, pattern matching) in LINQ expression trees, which normally only support a restricted subset of C#. It works by generating Expression<TDelegate> factory code at compile time from [Expressive]-decorated members.
dotnet restore
dotnet build --no-restore -c Release
dotnet test --no-build -c Release
# Run a single test project
dotnet test tests/ExpressiveSharp.Generator.Tests -c Release
# Run a single test by name
dotnet test --filter "FullyQualifiedName~MyTestName" -c Release
# Run tests against a specific target framework
dotnet test -f net8.0 -c Release
# Accept new Verify snapshots (for snapshot tests)
VERIFY_AUTO_APPROVE=true dotnet test tests/ExpressiveSharp.Generator.Tests
# Pack NuGet packages locally
dotnet pack -c Release
# Run benchmarks (all)
dotnet run -c Release --project benchmarks/ExpressiveSharp.Benchmarks/ExpressiveSharp.Benchmarks.csproj -- --filter "*"
# Run benchmarks (specific class)
dotnet run -c Release --project benchmarks/ExpressiveSharp.Benchmarks/ExpressiveSharp.Benchmarks.csproj -- --filter "*GeneratorBenchmarks*"CI targets the .NET 8.0, 9.0, 10.0, and 11.0 (RC) SDKs.
-
Compile-time (source generation): Two incremental generators in
ExpressiveSharp.Generator(targets netstandard2.0):ExpressiveGenerator— finds[Expressive]members, validates them viaExpressiveInterpreter, emits expression trees viaExpressionTreeEmitter, and builds a runtime registry viaExpressionRegistryEmitterPolyfillInterceptorGenerator— uses C# 13[InterceptsLocation]to rewriteExpressionPolyfill.Create()andIExpressiveQueryable<T>LINQ call sites from delegate form to expression tree form. Supports all standardQueryablemethods, multi-lambda methods (Join, GroupJoin, GroupBy overloads), non-lambda-first methods (Zip, ExceptBy, etc.), and custom target types via[PolyfillTarget](e.g., EF Core'sEntityFrameworkQueryableExtensionsfor async methods)
-
Runtime:
ExpressiveResolverlooks up generated expressions by (DeclaringType, MemberName, ParameterTypes).ExpressiveReplaceris anExpressionVisitorthat substitutes[Expressive]member accesses with the generated expression trees. Forvirtual/override[Expressive]members it dispatches polymorphically: it discovers derived[Expressive]overrides across loaded assemblies (cached, refreshed on assembly load) and emits a runtime type-test chaininstance is Derived ? <derived body> : <base body>(EF Core translates this to a TPH discriminatorCASE). Opt out globally viaExpressiveOptions.DisablePolymorphicDispatch()(EF:UseExpressives(o => o.DisablePolymorphicDispatch())); per-override[NotExpressive]opts a single type out. Transformers (inTransformers/) post-process trees for provider compatibility.
src/ExpressiveSharp.Generator/ExpressiveGenerator.cs— main generator entry pointsrc/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs— maps IOperation nodes toExpression.*factory calls (the heart of code generation). UsesvarPrefixto ensure unique local variable names across multi-lambda emitterssrc/ExpressiveSharp.Generator/Interpretation/ExpressiveInterpreter.cs— validates and prepares[Expressive]memberssrc/ExpressiveSharp.Generator/PolyfillInterceptorGenerator.cs— interceptor generation. Dedicated emitters for complex methods (Join, GroupJoin, GroupBy multi-lambda), enhanced generic fallback (EmitGenericSingleLambda) for single-lambda methods with non-lambda arg forwarding,[PolyfillTarget]support for custom target types. Emits each stub's declared receiver and return types, so stubs on derived markers such asIOrderedExpressiveQueryable<T>worksrc/ExpressiveSharp/Services/ExpressiveResolver.cs— runtime expression registry lookupsrc/ExpressiveSharp/PolyfillTargetAttribute.cs— specifies a non-Queryabletarget type for interceptor forwarding (e.g.,EntityFrameworkQueryableExtensions)src/ExpressiveSharp/Extensions/ExpressiveQueryableLinqExtensions.cs— delegate-based LINQ stubs onIExpressiveQueryable<T>(~85 intercepted + ~15 passthrough methods)src/ExpressiveSharp/IOrderedExpressiveQueryable.cs— zero-member marker bridgingIOrderedQueryable<T>andIExpressiveQueryable<T>, returned byOrderByand required byThenBy(matchingSystem.Linq.Queryable). Deliberately no ordered × includable cross-product:Include/AsNoTrackingmust precede the ordering.AsExpressivehas anIOrderedQueryable<T>overload so ordering interceptors re-wrap with no generator-side factory selectionsrc/ExpressiveSharp.EntityFrameworkCore/Extensions/ExpressiveQueryableEfCoreExtensions.cs— EF Core-specific stubs: chain-continuity (AsNoTracking, TagWith, etc.), Include/ThenInclude, and async lambda methods (AnyAsync, SumAsync, etc.)src/ExpressiveSharp.EntityFrameworkCore/IIncludableExpressiveQueryable.cs— hybrid interface bridgingIIncludableQueryableandIExpressiveQueryablefor Include/ThenInclude chain continuitysrc/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.Abstractions/WindowFunction.cs— public marker methods for all SQL window functions (ranking, aggregate, navigation). All throw at runtime; translated to SQL by the method call translatorsrc/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.Abstractions/WindowDefinition.cs— fluent builder types:PartitionedWindowDefinition→OrderedWindowDefinition→FramedWindowDefinition. Type-safe chain ensures ORDER BY before ranking, frame only on aggregates/value functionssrc/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.Abstractions/WindowFrameBound.cs— frame boundary markers:UnboundedPreceding(property),Preceding(n)(method),CurrentRow(property),Following(n)(method),UnboundedFollowing(property)src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions/Infrastructure/Internal/WindowFunctionMethodCallTranslator.cs— translatesWindowFunction.*calls toWindowFunctionSqlExpressionorRowNumberExpression. Ranking functions never emit frames; aggregates propagate frames; navigation functions pass through 1–3 argssrc/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions/Infrastructure/Internal/WindowFunctionSqlExpression.cs— self-rendering SQL expression:FUNC(args) OVER(PARTITION BY ... ORDER BY ... [ROWS/RANGE BETWEEN ...]). Provider-agnostic via SQL:2003 standard syntax
ExpressiveSharp.Abstractions (attributes + source generator, net8.0;net9.0;net10.0;net11.0)
└── no external deps
Provides: [Expressive], [ExpressiveFor], [ExpressiveForConstructor], [PolyfillTarget],
IExpressionTreeTransformer, source generator + code fixers (as analyzers)
ExpressiveSharp (core runtime, net8.0;net9.0;net10.0;net11.0)
└── ExpressiveSharp.Abstractions
Provides: ExpressiveResolver, ExpressiveReplacer, expression transformers,
IExpressiveQueryable<T>, ExpressionPolyfill, .ExpandExpressives(), .AsExpressive()
ExpressiveSharp.Generator (source generator, netstandard2.0)
└── Microsoft.CodeAnalysis.CSharp 5.0.0
ExpressiveSharp.MongoDB (net8.0;net9.0;net10.0;net11.0)
├── ExpressiveSharp
├── MongoDB.Driver 3.4.0
└── Provides: IRewritableMongoQueryable<T>, ExpressiveMongoCollection<T>,
ExpressiveMongoQueryProvider (decorating IQueryProvider/IMongoQueryProvider),
async lambda stubs with [PolyfillTarget(typeof(MongoQueryable))]
ExpressiveSharp.EntityFrameworkCore (net8.0;net9.0;net10.0;net11.0)
├── ExpressiveSharp
├── EF Core 8.0.25 / 9.0.0 / 10.0.0 / 11.0.0-rc.1 (per-TFM VersionOverride)
└── Provides: ExpressiveDbSet<T>, IIncludableExpressiveQueryable<T,P>,
chain-continuity stubs, async lambda stubs with [PolyfillTarget]
ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.Abstractions (net8.0;net9.0;net10.0 — no net11.0, see below)
└── no external deps
Provides: Pure marker types for window functions — no EF Core dependency:
WindowFunction (ranking: ROW_NUMBER, RANK, DENSE_RANK, NTILE, PERCENT_RANK, CUME_DIST;
aggregate: SUM, AVG, COUNT, MIN, MAX; navigation: LAG, LEAD, FIRST_VALUE,
LAST_VALUE, NTH_VALUE), Window, OrderedWindowDefinition,
PartitionedWindowDefinition, FramedWindowDefinition, WindowFrameBound
ExpressiveSharp.EntityFrameworkCore.RelationalExtensions (net8.0;net9.0;net10.0 — no net11.0, see below)
├── ExpressiveSharp.EntityFrameworkCore
├── ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.Abstractions
├── EF Core Relational 8.0.25 / 9.0.0 / 10.0.0 (per-TFM VersionOverride)
└── Provides: Window function SQL translation (ranking, aggregate with ROWS/RANGE
frame clauses, navigation with LAG/LEAD/FIRST_VALUE/LAST_VALUE/NTH_VALUE),
indexed Select, activated via UseExpressives(o => o.UseRelationalExtensions()).
Translators: WindowFunctionMethodCallTranslator, WindowSpecMethodCallTranslator,
WindowFrameBoundMemberTranslator (IMemberTranslatorPlugin for property getters).
Note: NTH_VALUE is not supported on SQL Server.
EF Core 11 is unsupported: both RelationalExtensions packages deliberately do
not target net11.0 — EF 11 removed the QuerySqlGenerator VisitChildren fallback
that WindowFunctionSqlExpression's self-rendering relied on, with no replacement
extension point (dotnet/efcore#37533; tracked in dotnet/efcore#38977). Test
projects gate on a RelationalExtensionsSupported msbuild property. Add the TFM
back (and drop those gates) once EF provides a hook.
ExpressiveSharp.EntityFrameworkCore.CodeFixers (Roslyn analyzer, netstandard2.0)
└── Microsoft.CodeAnalysis.CSharp.Workspaces 4.12.0
(packed into ExpressiveSharp.EntityFrameworkCore NuGet package)
Diagnostic codes span EXP0001–EXP0032 (with EXP0024 retired — see below — so the ID is reserved but unused) plus the migration band EXP1001–EXP1003. Generator diagnostics live in src/ExpressiveSharp.Generator/Infrastructure/Diagnostics.cs (EXP0001–EXP0012 core [Expressive], EXP0013–EXP0017 [ExpressiveFor], EXP0018–EXP0022 [ExpressiveProperty], EXP0023 ignored operation); analyzer diagnostics EXP0025–EXP0029 and EXP0032 in ExpressiveSharp.CodeFixers; window-function diagnostics EXP0030/EXP0031 in WindowFunctionLiteralArgsAnalyzer (EntityFrameworkCore.CodeFixers); migration EXP1001–EXP1003 in MigrationAnalyzer. The canonical reference is docs/reference/diagnostics.md. Key ones: EXP0001 (requires body), EXP0004 (block body requires opt-in), EXP0008 (unsupported operation, default value used), EXP0023 (unsupported operation ignored, e.g. alignment specifiers), EXP0016 ([ExpressiveFor] conflicts with [Expressive]), EXP0030 (Ntile non-positive literal), EXP0031 (Lag/Lead negative literal offset), EXP0032 (override of an [Expressive] member missing [Expressive]). EXP0024 (retired): virtual/abstract/override [Expressive] members used to warn that they would not dispatch polymorphically; they now do dispatch polymorphically at runtime (ExpressiveReplacer emits an is Derived ? … : base chain, translated to a TPH discriminator CASE), so the warning was removed and EXP0032 took over the derived-side guidance.
| Project | Purpose |
|---|---|
ExpressiveSharp.Generator.Tests |
Snapshot tests (Verify.MSTest) — validates generated C# output |
ExpressiveSharp.Tests |
Unit tests for runtime services, transformers, extensions |
ExpressiveSharp.IntegrationTests |
Compiles expression trees to delegates and runs them against in-memory collections — provider-agnostic correctness for every feature (arithmetic, switch, pattern matching, null-conditional, loops, tuples, [Expressive] expansion). Also hosts the shared Store scenario models + seed data. |
ExpressiveSharp.EntityFrameworkCore.IntegrationTests |
All EF Core integration tests — scenarios, async queryables, window functions, ExecuteUpdate, Include, query filters, conventions. Runs against SQLite by default; containerized SqlServer/Postgres/PomeloMySql/Cosmos via -p:TestDatabase=<provider> |
ExpressiveSharp.Benchmarks |
BenchmarkDotNet performance benchmarks (generator, resolver, replacer, transformers, EF Core) |
- Compilation — generated code compiles without errors
- Diagnostics — no warnings (TreatWarningsAsErrors is enabled globally)
- Behavioral — expression tree is functionally equivalent to the original code
Snapshot tests use GeneratorTestBase.RunExpressiveGenerator() to compile via Roslyn and compare output.
TreatWarningsAsErrors: true— all warnings are errorsNullable: enable— full nullable reference types- C# 14.0 on all TFMs (net8.0–net11.0;
LangVersionis set globally inDirectory.Build.props) - Allman brace style, 4-space indentation,
varpreferred - Instance fields:
_camelCase - Expression-bodied members preferred for methods/properties
- Generator targets netstandard2.0 — no modern C# APIs (no spans, no
Index/Range, limited BCL). All generator code must compile against netstandard2.0 even though the rest of the solution uses C# 12+/14. - Snapshot tests —
*.verified.csfiles alongside tests intests/ExpressiveSharp.Generator.Tests/are the expected output. Commit them. UseVERIFY_AUTO_APPROVE=trueto accept new baselines. - Incremental generator caching — never store live Roslyn objects (symbols, syntax nodes) in cached pipeline state. Use immutable data snapshots with custom equality comparers.
- Central Package Management — all package versions are pinned in
Directory.Packages.props. When adding a new dependency, add its version there (not in the individual.csproj).
Expressive_AllowBlockBody— global default for block-bodied[Expressive]members (defaults to false; can also be set per-member via attribute)