Proposal: Patterns for "as" to treat "unmatched" as null #8207
-
Note: There was another proposal (#402) using MotivationA lot of the language is built around pattern matching and option-like Class(string? a, string? b)
{
this.a = a != null ? a : "<unspecified>";
if(b == null) throw new ArgumentNullException(nameof(b));
this.b = b;
// can be easily simplified as
this.a = a ?? "<unspecified>";
this.b = b ?? throw new ArgumentNullException(nameof(b));
} Yet in situations where Class(string? a, int b)
{
this.a = a is not null or "" ? a : "<unspecified>";
if(b < 0) throw new ArgumentOutOfRangeException(nameof(b));
this.b = b;
// both "a" and "b" need to be referenced twice
} In many situations, there are special values outside of What if we were instead able to leverage the existing Extending patterns for
|
is syntax |
is semantics |
as syntax |
as semantics |
---|---|---|---|
x is T |
type check | x as T |
unchanged (cast to T? , or null on failure) |
x is T { ... } |
type and properties check | x as T { ... } |
cast to T? , ensuring the properties, or null on failure |
x is { ... } |
properties check | x as { ... } |
cast to the nullable equivalent of the type of x , ensuring the properties, or null on failure |
x is T and P |
type and P check |
x as T and P |
cast to T? (or a more specific applicable type from P ), ensuring P , or null on failure |
x is P |
P check |
x as P |
cast to the nullable equivalent of the type of x (or a more specific applicable type from P ), ensuring P , or null on failure |
The pattern may not create any new variables (since otherwise their use would require the check that the result is not null
).
Examples
object? o;
string s;
byte b;
s as { Length: > 10 }
‒ results ins
ifs is { Length: > 10 }
, ornull
(typestring?
).o as >= 0
‒ results in a value of typeint?
as either>= 0
ornull
.b as not 0
‒ results in a value of typebyte?
with the value ofb
if non-0, ornull
.o as not null
‒ just equivalent too
.
Uses
The code above could be easily simplified to:
Class(string? a, int b)
{
this.a = a as not "" ?? "<unspecified>";
this.b = b as >= 0 ?? throw new ArgumentOutOfRangeException(nameof(b));
}
Further examples
(timeout as not 0) ?? DefaultTimeout
‒ treat 0 as unspecified timeout. Can be read as "timeout
as not 0, otherwiseDefaultTimeout
".(myEnumValue as not MyEnum.Unspecified) ?? DefaultMyEnumValue
‒ same for anenum
value:numerator / (denominator as not 0)
‒null
if division would cause a black hole.(collection as not [])?[0]
‒ access first element if there is one.(entity as { Exists: true })?.Property
‒ access entity only if it still exists, otherwisenull
.stream.ReadByte() as not -1
‒ useful even alone so that you don't mistakenly use an invalidbyte
.IndexOf(...) as not -1
‒ likewise.obj as not null
‒ not useful much, just results inobj
(but makes the type nullable). Could be a warning.
Alternatives
An alternative syntax has been proposed, not depending on as
, with a similar effect:
Class(string? a, int b)
{
this.a = a when not "" else "<unspecified>";
this.b = b when >= 0 else throw new ArgumentOutOfRangeException(nameof(b));
}
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 5 replies
-
I've seen many suggestions on this repository on how to improve upon this general area, and they're often way too niche and with arcane symbols. This suggestion on the other hand - after reading it twice over, because I didn't fully understand what you were proposing the first time (compared to what already exists) - would fit into the language perfectly I think, as a natural evolution of the |
Beta Was this translation helpful? Give feedback.
-
I'm conflicted. I think the syntax is fine, but it's such a minor improvement to the current syntax using the conditional operator or switch expressions that I don't think that the implicit result of Personally, I'd rather see syntax for expressing an unconditional pattern-based destructuring than another way of doing conditional pattern matching. |
Beta Was this translation helpful? Give feedback.
-
I think I'm willing to champion this proposal, if for no other reason than to bring to LDM and get an initial gut check on it. @IS4Code, could you please open an issue with your proposal? The initial check doesn't need to be any more detailed than what you have here; if the LDM thinks there's something we'd be willing to see in the language here, we'd then need to expand out to a full proposal. |
Beta Was this translation helpful? Give feedback.
-
I would prefer Class(string? a, int b)
{
this.a = a when not "" else "<unspecified>";
this.b = b when >= 0 else new ArgumentOutOfRangeException(nameof(b));
} |
Beta Was this translation helpful? Give feedback.
I think I'm willing to champion this proposal, if for no other reason than to bring to LDM and get an initial gut check on it. @IS4Code, could you please open an issue with your proposal? The initial check doesn't need to be any more detailed than what you have here; if the LDM thinks there's something we'd be willing to see in the language here, we'd then need to expand out to a full proposal.