The special syntax is really just syntactic sugar on top of all this that makes things a little bit more readable for complex queries because e.g. you don't have to repeat computed variables every time after binding it once in the chain. Consider:
from x in xs
where x.IsFoo
let y = Frob(x)
where y.IsBar
let z = Frob(y)
where z.IsBaz
order by x, y descending, z
select z;
If you were to rewrite this with explicit method calls and lambdas, it becomes something like: xs.Where(x => x.IsFoo)
.Select(x => (x: x, y: Frob(x)) }
.Where(xy => xy.y.IsBar)
.Select(xy => (x: xy.x, y: xy.y, z: Frob(xy.y)))
.Where(xyz => xyz.z.IsBaz)
.OrderBy(xyz => xyz.x)
.ThenByDescending(xyz => xyz.y)
.ThenBy(xyz => xyz.z)
.Select(xyz => xyz.z)
Note how it needs to weave `x` and `y` through all the Select/Where calls so that they can be used for ordering in the end here, whereas with syntactic sugar the scope of `let` extends to the remainder of the expression (although under the hood it still does roughly the same thing as the handwritten code).Edit: what's also nice is that C# recognizes Linq as a contract. So long as this has the correct method names and signatures (it does), the Linq syntax will light up automatically. You can also use this trick for your own home-grown things (add Select, Join, Where, etc. overloads) if the Linq syntax is something you like.
[1]: https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotn...
Some notes on why this is so here: https://github.com/dotnet/runtime/blob/main/docs/design/core...
from x select x.name
And other is just lambda with anonymous types and so on.
For the lambda syntax, you can just do this: https://www.npmjs.com/package/linq
Of course, if you want to run this against a query provider, you do need compiler support to instead give you an expression tree, and provider to process it and convert them to a language (often sql) that database can understand.
There seems to be some transpilers, or things like that - but i don't know what the state of the art is on this: https://github.com/sinclairzx81/linqbox
1. You can extend other people's interfaces. If you care about method chaining, _something_ like that is required (alternative solutions include syntactic support for method chaining as a generic function-call syntax).
2. The language has support for "code as data." The mechanism is expression trees, but it's really powerful to be able to use method chaining to define a computation and then dispatch it to different backends, complete with optimization steps.
3. The language has a sub-language as a form of syntactic sugar, allowing certain blessed constructs to be written as basically inline SQL with full editor support.
Compare C# ORMs to JS/TS for example. In C#, it is possible to use expression trees to build queries. In TS, the only options are as strings or using structural representation of the trees.
Compare this:
var loadedAda = await db.Runners
.Include(r => r.RaceResults.Where(
finish => finish.Position <= 10
&& finish.Time <= TimeSpan.FromHours(2)
&& finish.Race.Name.Contains("New")
)
)
.FirstAsync(r => r.Email == "ada@example.org");
To the equivalent in Prisma (structural representation of the tree): const loadedAda2 = await tx.runner.findFirst({
where: { email: 'ada@example.org' },
include: {
races: {
where: {
AND: [
{ position: { lte: 10 } },
{ time: { lte: 120 } },
{
race: {
name: { contains: 'New' }
}
}
]
}
}
}
})
Yikes! Look how dicey that gets with even a basic query!
incoming1211•5h ago
bob1029•5h ago
There's an official process for API change requests: https://github.com/dotnet/runtime/blob/main/docs/project/api...
lmz•4h ago
CharlieDigital•4h ago
Backwards compatibility, security, edge cases, downstream effects on other libraries that are reliant on LINQ, etc.
One guy with an optional library can break things. If the .NET team breaks things in LINQ, it's going to be a bad, bad time for a lot of people.
I think Evan You's approach with Vue is really interesting. Effectively, they have set up a build pipeline that includes testing major downstream projects as well for compatibility. This means that when the Vue team build something like "Vapor Mode" for 3.6, they've already run it against a large body of community projects to check for breaking changes and edge cases. You can see some of the work they do in this video: https://www.youtube.com/watch?v=zvjOT7NHl4Q
mrmedix•4h ago
akdev1l•4h ago
I know of two examples:
1. Fedora in collaboration with GCC maintainers keep GCC on the bleeding edge so it can be used to compile the whole Fedora corpus. This validates the compiler against a set of packages which known to work with the previous GCC
2. I think the rust team also builds all crates on crates.io when working on `rustc`. It seems they created a tool to achieve that: https://github.com/rust-lang/crater
I would assume the .NET guys have something similar already but maybe there’s not enough open code to do that
zamalek•4h ago
C# has multiple technologies built to deal with ABI (though it probably all goes unused these days with folder-based deployments, you really need the GAC for it to work).
jasonjayr•1h ago
clscott•4m ago
qingcharles•3h ago
Every release has a fairly decent amount of fixes and additions from outside contributors, and while I can see a lot of to/fro on the PRs to get them through, it's probably not quite as bad as you'd expect.
nikeee•4h ago
Adding another enumerable type would be a very large change that could effectively double the API surface of the entire ecosystem. This could take some time. Some places still don't even support Span<T>. Also there were some design decisions related to Linq where the number of overloads were a consideration.
Adding this API to .NET could probably be done with that extension method that converts to ValueEnumerable. But without support for that enumerable, this would pretty much be a walled garden where you have to convert back and forth between different enumerable types. Not that great if you'd ask me, but possible I guess.
kevingadd•2h ago
The way LINQ currently works by default makes aggressive use of interfaces like IEnumerable to hide the actual types being iterated over. This has performance consequences (which is part of why ZLinq can beat it) but it has advantages - for example, the same implementation of Where<T>(seq) can be used for various T's instead of having to JIT or AOT-compile a unique body for every distinct class you iterate over.
From looking at ZLinq it seems like it would potentially have an explosion of unique generic struct types as your queries get more complex, since for it to work you potentially end up with types vaguely resembling Query3<Query2<Query1<T>>>>. But it might not actually be that bad in practice.
jayd16•52m ago
The Task library has successfully added ValueTask but it took some doing. LINQ on the other hand can be replaced with unrolled loops or libraries more easily so the pressure just hasn't been there.
I could see something happening in the future but it would take a lot of be work.