✅ Switch pattern matching with integers
Is this a valid/idiomatic way to use pattern matching to compare integers?
One thing bugging me in particular is: are the arms checked in the order they were written, or is the order not guaranteed?
If the order is not guaranteed, then obviously the code above does not work.
29 Replies
It is valid
As for the order... $tias
But I believe it does work in order
I mean, it could work even if the order wasn't guaranteed
so it's not really a "try it and see" kind of deal
the compiler could prefer the textual order but reserve the right to rearrange if it optimizes better
it's something that should be mentioned in the docs/spec, but I can't really find anything :/
u cant really say "order" here, the compiler transforms the code and there wont even be a switch statement anymore afterwards.
u can check that with sharplab:
https://sharplab.io/#v2:D4AQTAjAsAUCDMACciDCiDesHIgNkQBMB7AVwCMAbAU0QGVSBjR6gZ1YCUBDAF2oAoAlgDseiVgAdq1QgEpEAXgB8sRGvVrJ0wuIDugnowAWqjeqwwzVxAAZFS2wDobAGlPX1AHkQBWe4ghnN0sPL0QATn8bR3Dg0PVI5ScADjj4gLsk6IB2bLT4gH1/HiMAJ2JdRGFqSoBRAA8WCR5BYmF+ABIAIgapRj4dLRlEcmoeXWlhW0QuYR0IV0QAc2IxAAMMIcIAXzWu2XczbYBuWG2gA===
SharpLab
C#/VB/F# compiler playground.
also if thats real code and not an example, negative numbers will always go into the
< 5
branch thus result in 1.0
I think you're incorrect, as the official docs do state this:
The result of a switch expression is the value of the expression of the first switch expression arm whose pattern matches the input expression and whose case guard, if present, evaluates to true. The switch expression arms are evaluated in text order.In any case, the fact the compiler transforms the code doesn't matter insofar the transformed code respects the established contract (i.e. that the arms are evaluated in textual order). Fair enough! I personally would've used an unsigned number here as negative speed doesn't make sense in this situation, but this is just example code from The Internet(TM), so yeah
The compiler doesn't even allow you to define a pattern if all possible values of that pattern have already been handled by previous arms.
Thinker
REPL Result: Failure
Exception: CompilationErrorException
Compile: 456.687ms | Execution: 0.000ms | React with ❌ to remove this embed.
So yes, it's effectively "in order"
Just to be clear, what you mentioned previously (compiler prevents overlapping patterns) isn't a direct answer. You can have all disjoint patterns where having guaranteed order still matters.
For example, if you know there is a case that happens much more frequently, then knowing the the order is guaranteed makes it possible to put that arm first to get performance benefits.
I guess? What kinds of objects are you working with where this would matter?
I think the compiler tries to optimize patterns as much as possible
I'm not saying it does always matter, just that it's a consideration, at least theoretically.
the compiler can't know the runtime patterns of my application though, so that's a choice I have to make as a dev.
Again, what objects do you have where this would matter?
The worst-case scenario is one where you have a lot of arms, and you unknowingly put the case that is by far the most common as the very last arm
that means, if I understand correctly, that the code would perform tons of conditional checks before finally getting to the last arm
I'm not saying that it does always matter, I'm just saying it can be a concern
you have to understand that I come from a systems programming background, so these are the kind of issues I care about 🙂
Seems like order does matter
what i meant is that the order can be different to some degree as long as it doesnt change the behavior
eg, the transformed code checks first for < 5 and then for == 0
the compiler emits the equivalent of if/else blocks, in the order you listed, but that could easily get optimized into a different order by the JIT or by the CPU
the C# compiler will absolutely screw with the order
it is not guaranteed to be the same order as the one written in the code, even for simple cases
The docs are lying, then? https://discord.com/channels/143867839282020352/1153016618964758599/1153021513805611100
they are unclear. it will never mess with the order in a way that causes visible side-effects. (for this purpose, side-effects are
when
clauses. they are not property accesses or Deconstruct method calls, which may still be reordered, but are expected to behave well)
but it is pretty easy to see that, in cases without side effects, it absolutely doesn't just replicate the order of the code. take this example: it's the JIT/AOT compiler that's gonna do that, though, not Roslyn
not neccessarily!
the generated IL code for the above example looks like (after decompiling) you can see that Roslyn turned this into a binary search. this does the comparisons in a different order from the original source
in general, it will rearrange the order of the comparisons so as to minimize the number of comparisons required in the worst-case. or, to be more specific, the set of comparisons in the
switch
is turned into a DAG, the DAG is topologically sorted, and that is used as the order of comparisons. (this is not a guarantee, merely the current implementation.)
which addresses this concern in a way, though it makes it harder to manually "tune"hmmmmm
if you know that 0-9 is the overwhelmingly common case for this method, and you want to minimize the number of comparisons in that case, you would have to write the ifs manually. sorry. or, you can trust the compiler, and allow PGO to clean it up. that would indeed be the work of the JIT compiler
actually PGO can't clean it up yet. https://github.com/dotnet/runtime/issues/75733 i suppose they take PRs. :)
GitHub
PGO: Re-order blocks without side-effects · Issue #75733 · dotnet/r...
Consider the following example: static void Main(string[] args) { Console.WriteLine((int)'3'); for (int i = 0; i < 100; i++) { foo('3'); Thread.Sleep(16); } } [MethodImpl(MethodI...
reordering of blocks without side-effects is something STephen talked about coming in .NET 8, wasn't it?
Thanks for the insights and the discussions @jakenveina @reflectronic @angius, I appreciate it.