csharplang/meetings/2018/LDM-2018-07-16.md
2018-09-13 15:45:41 +02:00

5.7 KiB

C# Language Design Notes for Jul 16, 2018

Agenda

  1. Null-coalescing assignment
    1. User-defined operators
    2. Unconstrained type parameters
    3. Throw expression the right-hand side
  2. Nullable await
  3. Nullable pointer access
  4. Non-nullable reference types feature flag follow-up

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 ?? (x = y)). If x is not null, the assignment is never performed.

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. 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
  • <no attribute>: Warnings are off and unannotated types are oblivious

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