Champion "and, or, and not patterns" (VS 16.8, .NET 5)
- [ ] Proposal added
- [ ] Discussed in LDM
- [ ] Decision in LDM
- [ ] Finalized (done, rejected, inactive)
- [ ] Spec'ed
The idea is to add three new pattern forms
- pattern
andpattern - pattern
orpattern notpattern
The latter two would not be permitted to define pattern variables in the subpatterns (as they would never be definitely assigned).
We'd have to decide on syntax and precedence, and whether some mechanism such as parentheses could be used for grouping patterns to override precedence.
Examples:
switch (o)
{
case 1 or 2:
case Point(0, 0) or null:
case Point(var x, var y) and var p:
}
The latter two would not be permitted to define pattern variables in the subpatterns
For or patterns, as mentioned in https://github.com/dotnet/csharplang/issues/118 we can allow "identical types" for overlapping pattern variables,
switch (o)
{
// x must be of the same type in both patterns
case Point(0, var x):
case Point(var x, 0):
break;
case Point(0, var x) or Point(var x, 0):
break;
}
Alternatively we could use the "most specific common type" for overlapping variables,
switch (o) {
case A x:
case B x:
CommonBase c = x;
break;
}
but just identical-types would be still useful most of the time as demonstrated by F#.
I'm with @alrz, I think that allowing the operands of the or patterns to introduce pattern variables would be a powerful feature with the requirement that the pattern variables must have the same type and must be assigned by both sides.
An alternative to "most specific common type" is the intersection type A | B.
I vote for using &&, ||, ! and not introducing yet more operators that do almost the exact same thing.
@MgSam
IIRC that would introduce ambiguities when patterns are used as Boolean expressions.
https://github.com/dotnet/roslyn/issues/6235
Are intersection and union types too difficult to implement?
Since the first two join two separate patterns, am I correct in understanding that the following would be valid?:
if (o is IDictionary d and IEnumerable e) {
// object.ReferenceEquals(d, e) == true
// (assuming no boxing)
}
if (o is IDictionary and IEnumerable e) {
// e is IEnumerable only, there is no IDictionary variable created
}
@ufcpp How would intersection and union types help with case 1 or 2:?
@yaakov-h Regarding your first example, yes. The second example doesn't make sense as IDictionary is not a pattern.
Also consider implementing xor patterns (or ^ if one does not like the word xor).
It would of course -as with or- not be permitted to define pattern variables.
Also consider implementing
xorpatterns
Why?
@HaloFour What's better? A feature that's ambiguous for the developer or one that's ambiguous for the compiler?
If they can't implement with the existing operators, it's not worth doing. This will just cause endless confusion if added like this. You'll get people trying to write:
if(condition and condition || condition)
More generally, C# team, please stop adding brand new keywords for minor use cases. The in disaster should be enough of a lesson for the reason why.
@MgSam What's worse is one that is ambiguous for both the developer and the compiler.
@MgSam I wouldn't call patterns a "minor use case".
if(condition and condition || condition)
This is not a pattern, or anything else that is or would be accepted by the compiler.
@MgSam You suggestion gives two different contradictory interpretations for this code, one of which is accepted today.
if (myBool is true || false)
@jnm2
Why?
~First to be consistent with or and and.
Second: You often will find a case where you are semantically asking, whether an object X is either A or B, e.g. when checking for numeric non-continuous ranges, discriminated unions. ...~
Forget it - xor is a bad idea.
With and-patterns and or-patterns we will finally have a way of comparing a variable against a given list of multiple values, just as every newbie programmer wants to do in any language :grin:
if(x is 1 or 2 or 3)
{
// Hooray!
}
what if we add this in the LHS of the is operator 🤔
if (x and y is not null)
@Richiban newbie may do with a IEnumerable (Here i use an array).
(new []{1,2,3}).Contains(x)
@alrz
if (x and y are not null)
FTFY. :wink:
if (neither x nor y are null)
😁
This is too funny please staph
@jmn2: Looks suspiciously like VB.NET...... (.__.)
If Neither x Nor y Are Nothing Then
' Just kiddin' - although it would funny to see,
' whether the VB compiler accepts Shakespeare's works as valid code
End If
(x, y) = default; still doesn't seem to work. 😢
@jnm2 We'll have to wait for 8.0 (https://github.com/dotnet/csharplang/issues/1394)
All of this can be done with some creativity. Newbs should be learning how to do things like this with what the language already has.
As no-one's mentioned this yet: this is particularly important for switch expressions. Consider this existing code:
public int Foo(int input)
{
switch (input)
{
case 0:
case 1:
return SomeMethod1() + SomeMethod2();
case 2:
return -1;
default:
return input;
}
}
This looks like it should be refactored to an expression-bodied method with a switch expression, but until the feature of this issue is implemented, that can't be cleanly done without either a guard clause or code duplication:
// Duplication approach
public int Foo(int input) => input switch
{
0 => SomeMethod1() + SomeMethod2(),
1 => SomeMethod1() + SomeMethod2(),
2 => -1,
_ => input
};
// Guard clause approach (which I suspect will be less efficient)
public int Foo(int input) => input switch
{
_ when input == 0 || input == 1 => SomeMethod1() + SomeMethod2(),
2 => -1,
_ => input
};
If this feature is adopted, or something similar, we get the much more pleasant:
public int Foo(int input) => input switch
{
0 or 1 => SomeMethod1() + SomeMethod2(),
2 => -1,
_ => input
};
It looks like this was briefly discussed in LDM: https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-03-19.md#and-or-and-not-patterns-1350
If this is outside the scope for C# 8.0, will we get the in pattern instead or some other way of matching multiple patterns inside a switch expression? If not, that could be a significant limitation forcing us to keep using the switch statement in many cases.
Is this really important? What are the benefits offered by this compared to the standard operators (||, &&, and !)?
@Lifeburner: Those operators don't work to compose patterns, and couldn't do so without more work, as per Neal's if (myBool is true || false) example. Fundamentally this is composing patterns rather than values, which I think is an important distinction.