Replies: 43 comments 190 replies
|
previous discussion: https://github.com/dotnet/roslyn/issues/2060 notable things missing:
|
|
Adding comment from issue #503: Expression Trees should be extended to support the C# 6.0 null propagating operator so that it can be used with Linq, with a translation to allow providers such as Linq to SQL to add support. The discussion on CodePlex doesn't seemed to have moved over to here, and has been sitting around for 3 years - I think it is worth revisiting, and ideally implementing. An ideal use case is an Outer Join in Linq to Objects. |
|
+1 |
|
I'd like to add my vote for this proposal for the sake of the Moq 4.x mocking library, whose API relies heavily on expression tree lambdas. In particular, support for the following two language features in expression tree lambda would be hugely beneficial to Moq. We could then fix the majority of currently reported bugs, and add some long requested features: I'm happy to further explain my reasons for prioritising these two, but don't want to go off on a tangent unbidden. |
|
With the (awesome) addition of nullable reference types in C# 8.0 having null-propagation operation in expression trees goes from "very important" to "you almost can not use Entity Framework without this feature". Looking forward for knowing the design details of how this could be implemented in order to adapt my LINQ provider. |
I wonder if local functions could be translated to SQL CTEs. |
|
Bump. @gafter, I was wondering whether there's any news from the C# language team about this. I understand that it's probably more interesting to design and plan brand-new language features like Is there any chance this can be given a higher priority? Is there anything the community can do to help advance expression trees? |
|
Async/await support is probably the largest undertaking on that list, but I'd love it. It would significantly help projects performing complex code-gen. |
|
@scalablecory - I totally agree this would be nice. That being said, I question the likeihood of getting I think it would be awesome if we could just get the ball rolling, i. e. we could get agreement from the C# language team and/or the Roslyn team to look at the most basic missing features for a start. (I'd be happy to help in any way I reasonably can.) If we manage to get there, perhaps the more complex features will eventually follow, too. |
|
It was discussed briefly on Monday... noting for example that we currently do not have support for /cc @dotnet/ldm It would help if we had a member of the LDM who championed this (even though it isn't technically a language feature). Any takers? |
|
I think there would need to be a language change to support statements in expression trees because expression tree types explicitly states:
emphasis added. For everything else:
Should it be (libraries that walk these often assume specific patterns; the fact that according to the language spec they appear to be able to change between compiler versions is surprising isn't it)? (OT, it looks like there are numerous spaces missing from this document after the word "expression"; are PRs being accepted for these sorts of things yet?) |
|
@bbarry PRs to fix typos in the spec are most welcome. |
|
@gafer Is there any issue where the changes on the expression API are being discussed? |
|
Not that I am aware |
|
I don’t see ?. in expression trees anywhere in the selected features for c# 8. Any chance it will be included? Is considered a part of nullable reference types? I had three bugs in production because of SQL implicit nulls in left joins... What can we do to help this move forward? |
|
A lot of the concern on this thread speaks to the use of expression trees in EF, but I also wanted to highlight their extensive use in the somewhat recently open-sourced Reaqtive project. This project provides a platform for hosted reactive operations allowing for stateful stateful checkpointing and recovery of standing and active subscriptions. It provides libraries for serialization of expression trees (allowing for queries and their components to be saved in stateful storage while at rest), which is huge in itself. However, the implementation of and extendibility of the observers and observables could be made dramatically simpler if .NET had support for async expression trees, so I'd ask that these be considered in the next overhaul of expression trees, whatever shape that takes. I understand the argument in here for breaking old dependencies on existing expression trees, but I don't necessarily see why this isn't something that is run in the .NET Core style of move-faster-likely-breaking-things as things transitioned from .NET 4 to .NET 5. Given that the implementation of each of the proposed changes is thought to be pretty straightforward and because Bart has already done some exploration in what might be needed to support different branches of support with the ExpressionTreeLike types and even has a sample implementation of this, why can this not simply be something that is opt-in, similar to that of the nullability flag in csproj wherein developers can choose to use the legacy expression tree (selected by default) or can opt into a more modern expression tree that will continually evolve as C# does. I don't know to what extent .NET is able to do feature flags, as it were, in the code, but what if modern expression support were an opt-in on a feature-by-feature basis? Today, my .csproj contains: <PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>Why can't modern expression trees be similar? This would enable all available modern expression tree functionality (similar to setting language support to vNext in that it's likely something will break with new changes). <PropertyGroup>
<ModernExpressions>true</ModernExpressions>
</PropertyGroup>And especially since there's such a laundry list of individual features, allow them to be opt-in by name: <PropertyGroup>
<ModernExpressions>
<NullCoaselscing>true</NullCoalescing>
<Async>true</Async>
<Dynamic>true</Dynamic>
</ModernExpressions>
</PropertyGroup>Those features that are inextricably linked to all of them are included just by enabling ModernExpressions (doesn't happen often, but does happen per the opt-in contract). But this means you don't have to enable all the new features all at once in a single release and trivially opens the door to revisiting to add new expressions features in a way that people can use if they want or simply opt-out of. At launch, there's zero net impact until developers decide to proactively opt into the new functionality - a great many will, but if their application breaks, I suspect they'll be more reasonable about the migration. After launch, there's some impact as developers upgrade packages indiscriminately, but the analyzer saves them from themselves here. And in the longer-term, new applications get to enjoy rich expression tree support and older applications keep on using the legacy approach. |
|
It's strange that something as important as this is not even being discussed. What's blocking this?! |
|
Found this discussion while writing yet another EF query that required me to do .Select(x => new Dto {
Foo = x.Foo == null ? x.Date : x.Foo
})instead of the sane thing, .Select(x => new Dto {
Foo = x.Foo ?? x.Date
})and I'm guessing I'll have to use it a while longer still |
|
Having just re-read this entire thread, all the while carefully considering the concerns and explanations provided by the likes of @CyrusNajmabadi, @333fred, et al. I must say they are making about as much sense as how many "new" C# syntactic features have had support added for them in expression trees over the last decade or so, which is, of course, a glorious zero. Let's consider this comment by @333fred — as I think it succinctly and clearly presents the supposed justification:
(emphasis mine) Sounds reasonable, right? Except that it should take no more than 5 seconds for anyone remotely familiar with C#/.NET's and the surrounding ecosystem's progression over the last couple of years, to realize two things, which immediately obliterate this reasoning: Swap the terms "new node" with "new built-in anything" and you'll get an equally sound argument for never adding any feature at any level in any area to the framework/standard library ever; because "what about existing tools that won't immediately support it?", I guess. It is unbelievably unconvincing. There is literally a fundamental analogy here between any hypothetical new expression nodes, and any new standard constructs in general — with the most notable recent example being what happened with Notice that according to the same logic as the one that we're being provided here as to why expressions are stuck at the C# of 10 years ago, apparently the addition of
It gets even worse, this argument is, in fact, even LESS applicable in the case of expression trees, because all consumers of expression trees (most prominently EF), ALREADY ONLY support a subset of "all possible expression trees". An expression tree consumer not supporting a certain thing at a given time is already the status quo, and as such, fully expected. Funny. Some people in this thread (e.g. @olmobrutall here) tried to point out the same thing, heck, even some comments on related Stack Overflow questions made this point, and made more basic logical sense than anyone from the other side here. I'm just trying to help reveal the folly in this reasoning more clearly and bring more awareness to the issue. |
|
Hello, just wanting to make sure I'm on the same page, since there are seemingly two different discussions going on. Forgive me if this is re-hashing the old points again, but I feel this might help more than me. There are fundamentally two different parts to this discussion. Things Currently Supported By Raw Expression Nodes, But Not Supported In Lambda ExpressionsThis includes things like CSharpExpressions was already mentioned as an example of solving some of those by implementing new nodes, but allowing most nodes to be reduced to a more complex tree using existing expression nodes. However, another example which does get used in production is LINQKit. Because it's the only way I can easily call a typed lambda expression from another lambda expression. Things Not Currently Supported By Raw Expression NodesThese are things which would explicitly require new node types to work. An example noted by CSharpExpressions is below.
My Issue / QuestionSomeone please correct me if I am misunderstanding, but I don't understand the controversy of fixing the first issue. The only possible downside is it would allow someone to more easily write expressions which contain some node types not supported by all query transformers. However, it is possible today to write those same not supported expressions today. They just have to be done by hand. Meanwhile, the convenience of the null-coalescing operator ( Plus, without LINQKit everything either has to be in one large lambda, violating almost every code readability standard out there, or everything which calls something else must be a hand constructed expression. Which is something most C# programmers, including the rest of my team, aren't familiar with and can't work on. Would someone please explain to me why fixing this first issue is so controversial. |
|
You should just rip off the bandaid and start pushing updates to the API. If you want to play it safe - do it on a small surface area of new language features to give the community / providers time to update for compatibility. Then on the next C# release push out the next wave of updates once library authors get the gist of what’s to come. Stop overprotecting the community though. We will pick up the pieces. PR’s will be raised, compatibility issues will be fixed. In MUCH less time than it takes you guys to debate all the downsides of how to minimized breakage. Let’s get on with it already! |
|
I honestly believe this feature is much more needed than Span or Memory since most of big players already manage memory properly where its needed, but runtime code generation is still missing, which is a blocker for many |
|
I'd love to voice support for an evolution of expression trees as well. Since it was mentioned the depriorization of work on this is due to not meeting the bar for investment/not having enough customer relevancy compared to other features, I thought I'd just throw in my anecdotal experience. In 4 quite different types of jobs that I've worked in over the past 15 years or so, I always ended up working on something where using expression trees was extremely beneficial, if not borderline inevitable. And I should say, none of these use cases were at all related to EF Core (just mentioning as my impression is much of the discussion - understandably - seems to gravitate a bit around Entity Framework. But there are so, so many other great use cases too!). My typical use case is almost always performance related. As random examples, my last use cases, both from within the past few weeks alone: So oftentimes stuff that is typically highly reflection based, but needs to be made significantly more efficient for some type of repeated invocations. The big issue for me is that there isn't much of an alternative really. Sometimes you can work your way around compiling a new assembly on the fly via Roslyn and loading that up. I've done that for another recent project. But in my view, it's not a great replacement in many cases - personally,
As for the elephant in the room, i.e. compatibility concerns: I definitely understand those in the context of query providers (at least somewhat; albeit last time I used EF, many many years ago, only some expressions were supported anyway, so you never quite knew what worked or not until runtime - did that change?). Long story short: Support for modern features in expression trees is sorely missed! For my personal work (!), this would be a much more beneficial than most other ongoing language features at the moment, so I'd definitely be really excited if those were revisited! |
|
Linq recently got some new features (LeftJoin and RightJoin) hence the argument made in this comment may not be a good reason anymore to avoid any changes in the library. |
|
Perhaps a library-only solution using interceptors could work? Something like and a source generator could intercept the call and generate appropriate factory method calls? |
|
Maybe I can add some context here. We did make a big change to expression trees in recent memory: the move to Roslyn. The compiler is responsible for encoding the tree represented in expression trees, and when we rewrote the compiler to C# in Roslyn we rewrote the implementation for the tree. This was tricky because the native compiler actually just dumped the in-memory tree into the expression tree. Roslyn doesn't use the same tree, so we had to map from the new Roslyn trees to the old native trees. We did a pretty good, but not great, job. There were a number of bugs around different nodes being generated for the same source. This is how we discovered that providers are very sensitive to the exact shape of the tree. There were many bugs filed around tree differences. At first we tried to fix those. However, it didn't take long for us to get a new set of bugs about how "fixing" the tree broke their provider since they had adapted to the new tree. This was made worse by the fact that dumping implementation details (the native compiler's tree) into public surface area was not good. Some of the trees were almost impossible to generate well from Roslyn. And some of the trees were just incorrect, because the native compiler had a kind of buggy or incorrect internal representation of a particular syntax. Roslyn has this new thing that is a public, supported, designed surface area for semantic nodes ( Roslyn has also added a bunch of source generation features that overlap with expression tree scenarios. They also address some big problems with expression trees. For instance, expression trees allow you to express things that would be illegal in C#, like violating accessibility. While this is useful, it's also problematic. By generating source that split is avoided. It also enables Native AOT support since certain expression trees require dynamic code compilation to execute, and all expression trees have to be interpreted otherwise. Source generation usually creates compatibility around build time, not run time. There are limitations to source generation but also benefits in that you usually figure out the problems when you recompile, not when you update the .NET runtime in production. Lastly, there's the matter of testing. While the test suite isn't bad, it's not to the level of completeness that would catch all possible breaks, as can be seen with the previous fixes. That makes changes riskier and more expensive. All together, the state of expression trees is a challenging environment to make changes in. While improvements could provide benefit, any change would have to carefully weigh cost and risk to be accepted. |
|
If this is all about breaking changes, can't we just have an assembly level attribute or something in the csproj that states "UseLegacyExpressionTreeParser" for backwards combatibility? If its's something that could be optionally applied even lower (class/method) then we could use a mix of legacy vs new technology. Tbh, this attitude of "we can't move forward because it will break consumers" doesn't seem to make sense when basic things like null propagation in lambdas is such a massively sought after feature. (It's also worth noting that probably 90% of the people coming across this issue just want null propogation in their expression trees - adding that alone would bring so much benefit, let's not make 'perfect' the enemy of 'good' here) :) |
|
Guys it's 2026. Previously unsolvable problems like this one have drastically different tools available to solve. Why doesn't the .NET team ship a Expressions Trees migration agent, and why don't you perform a Github-wide analysis of all libraries that would break, and why don't you generate a PR for the top libraries with the said migration agent and submit it to the authors for review as part of a pre-migration phase. You don't need to block on innovation for literally decades due to being bound by old norms. This is obviously so an agents-solvable problem, since it's a focused specialized skillset that can be dog-fooded on numerous internal libraries and tested out on so many public open-source codebases, then easily rolled out to the masses. Can release a one-click to install agent skill with a landing page for library owners showing them a video of how they can issue the agent to run on their codebase. Let's get with it team! |
|
I see the main limitation of expression trees as mixing concerns between executable code representation and query definition. Expression tries to represent both executable .NET code (Compile()) and query semantics, while most query providers only need a semantic representation. If Roslyn provided a mechanism to translate bound lambdas into library-defined expression-like types, libraries could decide which language constructs they support and how to represent them. The compiler would still handle parsing, binding, overload resolution, and diagnostics, while providers would only define translation into their own query model. This could let providers support modern C# features incrementally without being constrained by System.Linq.Expressions. |

Uh oh!
There was an error while loading. Please reload this page.
Expression trees today support a limited subset of expression lambdas. This issue is to track the request to extend them to support more or all of the language constructs that can be expressed in a lambda.
A prototype for work in this direction can be found on https://github.com/bartdesmet/ExpressionFutures/tree/master/CSharpExpressions and https://github.com/bartdesmet/roslyn/tree/ExpressionTrees (based on a fork of Roslyn a while ago).
/cc @bartdesmet
All reactions