2018-07-10 02:07:16 +02:00
|
|
|
# C# Language Design Notes for Jul 16, 2018
|
|
|
|
|
2018-07-18 01:16:59 +02:00
|
|
|
## Agenda
|
2018-07-10 02:07:16 +02:00
|
|
|
|
2018-07-18 01:16:59 +02:00
|
|
|
1. Null-coalescing assignment
|
|
|
|
1. User-defined operators
|
|
|
|
1. Unconstrained type parameters
|
|
|
|
1. Throw expression the right-hand side
|
|
|
|
1. Nullable await
|
|
|
|
1. Nullable pointer access
|
|
|
|
1. Non-nullable reference types feature flag follow-up
|
2018-07-10 02:07:16 +02:00
|
|
|
|
2018-07-18 01:16:59 +02:00
|
|
|
# Null-coalescing assignment
|
|
|
|
|
|
|
|
This feature has the syntax `??=` and only assigns the left-hand side of the
|
|
|
|
assignment if the left-hand side evaluates to null.
|
|
|
|
|
|
|
|
## User-defined operators
|
|
|
|
|
|
|
|
The proposal reads, `if (x == null) x = y;`
|
|
|
|
|
|
|
|
**Conclusion**
|
|
|
|
|
|
|
|
This should instead read `if (x is null) x = y;`. The difference is in the
|
|
|
|
operators: null-coalescing assignment, like the null-coalescing operator,
|
|
|
|
does not consider user-defined operators like `==`. The spec also states that
|
|
|
|
the semantics are equivalent to `x = x ?? y`, but that should instead be `(x
|
2018-09-13 15:45:41 +02:00
|
|
|
?? (x = y))`. If `x` is not null, the assignment is never performed.
|
2018-07-18 01:16:59 +02:00
|
|
|
|
|
|
|
|
|
|
|
## Result type of the expression
|
|
|
|
|
|
|
|
Other assignments have the type of the left-hand side e.g., `(x = y)` has the
|
|
|
|
type of `x`, not `y`. This has the convenient property that any assignment
|
|
|
|
expression can be replaced with the variable being assigned. However, there
|
|
|
|
may be some value in "transforming" the type to non-nullable when possible.
|
|
|
|
For instance, `(x ??= y)` could evaluate to a non-null type if `y` is
|
|
|
|
non-null, since we know that the null-coalescing assignment cannot produce
|
|
|
|
null. This is how the existing null-coalescing operator works. The
|
|
|
|
null-coalescing operator also specifies that, in `x ?? y`, the conversion to
|
|
|
|
a non-nullable version of `x` is preferred if it is available.
|
|
|
|
|
|
|
|
**Conclusion**
|
|
|
|
|
|
|
|
Let's favor the semantics of assignment. The return type should be the type of `x`.
|
|
|
|
|
|
|
|
## Null-coalescing assignment on unconstrained type parameters
|
|
|
|
|
|
|
|
Null coalescing currently isn't allowed on type parameters, so the proposal
|
|
|
|
currently doesn't allow it either.
|
|
|
|
|
|
|
|
**Conclusion**
|
|
|
|
|
|
|
|
This is a hole in null-coalescing as well. Both features should be allowed
|
|
|
|
on unconstrained type parameters.
|
|
|
|
|
|
|
|
## Throw expression on the right-hand side
|
|
|
|
|
|
|
|
Should the following be allowed: `a ??= throw new Exception`? `throw`
|
|
|
|
expressions are only allowed in certain places right now, so we would have to
|
|
|
|
explicitly add support. It makes more sense if the feature is equivalent to
|
|
|
|
`a = a ?? b` where `b` is a `throw`, than `a ?? (a = b)`.
|
|
|
|
|
|
|
|
**Conclusion**
|
|
|
|
|
|
|
|
Not supported
|
|
|
|
|
|
|
|
# Logical boolean compound assignment
|
|
|
|
|
|
|
|
Also proposed are the features `&&=` and `||=`. Should these features live
|
|
|
|
in the same proposal or be split out on their own?
|
|
|
|
|
|
|
|
**Conclusion**
|
|
|
|
|
|
|
|
Split them out. These features are potentially much more complicated because
|
|
|
|
user-defined conversions for `true`, `false`, `&`, and `|` could come into play.
|
|
|
|
|
|
|
|
# Null-conditional await, foreach
|
|
|
|
|
|
|
|
`await?` is proposed, which would only await the task-like target if it is
|
|
|
|
non-null and evaluate to null otherwise. A new syntax would be needed here
|
|
|
|
because the result produces a different type. `await` will currently always
|
|
|
|
have the "unwrapped" type from the task-like target, whereas `await?` would
|
|
|
|
need to make that type nullable in the case that it is not already. An
|
|
|
|
analogous feature is proposed for `foreach?`.
|
|
|
|
|
|
|
|
In favor of the feature, it may be more useful with nullable reference types
|
|
|
|
and more nullable Task-like things. On the other hand, it could lead to more
|
|
|
|
use of nulls in places that some of the design team considers code smells,
|
|
|
|
like widespread use of null Tasks or IEnumerable.
|
|
|
|
|
|
|
|
**Conclusion**
|
|
|
|
|
|
|
|
Probably not in C# 8, but maybe later. It may still have value, but probably
|
|
|
|
not as much as other features.
|
|
|
|
|
|
|
|
|
|
|
|
# Nullable pointer access expressions
|
|
|
|
|
|
|
|
The proposal includes `?->` and `?[]`. It purposefully leaves out `*?` as
|
|
|
|
being confusing as a prefix operator.
|
|
|
|
|
|
|
|
**Conclusion**
|
|
|
|
|
|
|
|
We don't love it. Not planned for any release. We may consider an external
|
|
|
|
contribution.
|
|
|
|
|
|
|
|
# Non-nullable reference type feature flag follow-up
|
|
|
|
|
|
|
|
We followed-up on the feature flag design from [last Wednesday's
|
|
|
|
LDM](LDM-2018-07-11.md). The main concern was making the language semantics
|
|
|
|
affected by a non-language option, namely a compiler flag. It was proposed
|
|
|
|
that the NonNullTypes attribute could effectively cover the same information
|
|
|
|
as the feature flag and the attribute together.
|
|
|
|
|
|
|
|
Proposal:
|
|
|
|
|
|
|
|
* `[NonNullTypes(true)]`: Warnings are on and unannotated types are non-nullable
|
|
|
|
* `[NonNullTypes(false)]`: Warnings are on but unannotated types are oblivious
|
2018-07-19 16:26:34 +02:00
|
|
|
* `<no attribute>`: Warnings are off and unannotated types are oblivious
|
2018-07-18 01:16:59 +02:00
|
|
|
|
|
|
|
The biggest benefit: if the warning opt-in is in source code, the nullable
|
|
|
|
warnings are not a breaking change. It's also noted
|
|
|
|
that this is similar to how CLS compliance already works with the
|
|
|
|
CLSCompliant attribute.
|
|
|
|
|
|
|
|
One downside is that there is no way to turn the warnings off once there is a
|
|
|
|
`NonNullTypes` attribute anywhere in the code. A solution is to provide a
|
|
|
|
`Warnings` property on the type which lets the user configure warnings. This
|
|
|
|
property would be true by default, but the user could override it e.g.,
|
|
|
|
`[NonNullTypes(true, Warnings = false)]`. Also, it's unclear how this would
|
|
|
|
work in environments like scripting. In normal compilations it's easy to find
|
|
|
|
somewhere to put an attribute, but that's not necessarily the case in
|
|
|
|
scripts.
|
|
|
|
|
|
|
|
**Conclusion**
|
|
|
|
|
|
|
|
Accepted with some notes:
|
|
|
|
|
|
|
|
* `[NonNullTypes]` means `[NonNullTypes(true)]`
|
|
|
|
* To prevent upgrading from C# 7 to C# 8 being a breaking change, we should
|
|
|
|
poison the `[NonNullTypes]` attribute using `ObsoleteAttribute` (like `ref`
|
|
|
|
structs), but provide a better message
|
|
|
|
* `?` in a context where there is no attribute or [NonNullTypes(false)] should be
|
|
|
|
a warning since the user is probably misunderstanding how to use the feature. `!`
|
|
|
|
is still useful even if unannotated types are oblivious, so it is only a warning
|
|
|
|
if there is no `NonNullType` present at all
|