119 lines
5.3 KiB
Markdown
119 lines
5.3 KiB
Markdown
|
# C# Language Design Notes for Jun 27, 2017
|
||
|
|
||
|
*Quotes of the day:*
|
||
|
> "Let's support most operators, but not equal and not-equal"
|
||
|
|
||
|
> "We'll rename the next version to C# l'eight"
|
||
|
|
||
|
## Agenda
|
||
|
|
||
|
1. User-defined operators in interfaces
|
||
|
2. return/break/continue as expressions
|
||
|
|
||
|
|
||
|
# User-defined operators in interfaces
|
||
|
|
||
|
## Conversion operators
|
||
|
|
||
|
Let's not allow conversion operators in interfaces, since they are not allowed *on* interfaces today, even when defined elsewhere.
|
||
|
|
||
|
## Equality operators
|
||
|
|
||
|
There'd be no way to override `Equals` and `GetHashCode`, so you couldn't do `==` and `!=` in a recommended way. That's an argument for disallowing. What if we *did* allow `Equals` and `GetHashCode` to be overridden in interfaces? That would be a major undertaking, for what seems like modest gains.
|
||
|
|
||
|
However, if we allow `<=` and `>=` you can get the effect of `==` anyway, through `&`. This may or may not be an argument for disallowing *more* - or maybe all - operators.
|
||
|
|
||
|
Some options:
|
||
|
|
||
|
1. Drop operators across the board
|
||
|
2. Let's not allow `==` and `!=` and see what feedback we get
|
||
|
3. Let's allow `==` and `!=` and see what people use it for
|
||
|
|
||
|
### Conclusion
|
||
|
Let's stick with option 2 for now. We'll see from prototype use whether this is an intolerable limitation, but we believe it is a decent place to land.
|
||
|
|
||
|
|
||
|
## Is the remainder worthwhile?
|
||
|
|
||
|
Conversion and equality are the most useful operators, in that they apply to any domain, whereas most of the other operators are for more specific, often math-related, purposes. If we don't allow them, is it worth allowing the rest?
|
||
|
|
||
|
### Conclusion
|
||
|
Let's assume that it is. Those specific domains are likely still important to express in interfaces.
|
||
|
|
||
|
|
||
|
## Lookup rules for operators
|
||
|
|
||
|
We only even look for operator implementations in interfaces if one of the operands has a type that is an interface or a type parameter with a non-empty effective base interface list.
|
||
|
|
||
|
### Unary operators
|
||
|
The applicable operators from classes/structs shadow those in interfaces. This matters for constrained type parameters: the effective base class can shadow operators from effective base interfaces.
|
||
|
|
||
|
### Binary operators
|
||
|
Here there could be operators declared in interfaces that are more specific than ones implemented in classes.
|
||
|
|
||
|
We should look at operators from classes first, in order to avoid breaking changes. Only if there are no applicable user-defined operators from classes will we look in interfaces. If there aren't any there either, we go to built-ins.
|
||
|
|
||
|
We hypothesize that there's no case (outside of equality and conversion) where user-defined operators in interfaces shadow predefined operators, but we may be wrong. So that needs to be investigated a bit.
|
||
|
|
||
|
|
||
|
## Shadowing within interfaces
|
||
|
|
||
|
If we find an applicable candidate in an interface, that candidate shadows all applicable operators in base interfaces: we stop looking. This is a bit more complicated than in classes, where it's linear, but we can express it right.
|
||
|
|
||
|
|
||
|
# return/break/continue as expressions
|
||
|
|
||
|
It's been proposed to allow `return`, `break` and `continue` as expressions, the same way we now allow `throw` expressions.
|
||
|
|
||
|
There's a bigger discussion of whether we should seek to make C# more expression-oriented, adding a "value" (or at least a classification) for each statement kind, and allowing all or most statements in an expression context. However, this specific proposal does not need to be part of that discussion: we already have `throw` expressions, and the potential value of adding these other *jump-statement*s can be evaluated in that narrower context.
|
||
|
|
||
|
There are clearly scenarios that can be expressed more concisely with this feature, but the really compelling ones involve the use of `??` on nullable value types (and presumably on nullable reference types in the future). It provides an elegant way to get at the underlying type without having to more explicitly unpack it:
|
||
|
|
||
|
``` c#
|
||
|
int z = x + y ?? return null; // where x and y are of type int?
|
||
|
|
||
|
//as opposed to something like
|
||
|
if (x is null || y is null) return null;
|
||
|
int z = (x + y).Value; // explicit unpacking
|
||
|
|
||
|
// or
|
||
|
int? nz = x + y;
|
||
|
if (nz is null) return null;
|
||
|
int z = nz ?? 0; // dummy unpacking
|
||
|
|
||
|
// or using patterns
|
||
|
if (!(x + y is int z)) return null;
|
||
|
```
|
||
|
|
||
|
The scenarios involving the ternary conditional operator seem less compelling, as they are more easily replaced by a "bouncer" if-statement:
|
||
|
|
||
|
``` c#
|
||
|
accumulator += (y != 0) ? x/y : continue;
|
||
|
|
||
|
// as opposed to
|
||
|
if (y == 0) continue;
|
||
|
accumulator += x/y;
|
||
|
```
|
||
|
|
||
|
Yes, it's more concise, but is it more clear?
|
||
|
|
||
|
`break` and `continue` differ from `throw` in that the latter just gets out of there, whereas these affect definite assignment more subtly and locally.
|
||
|
`return` feels more like `throw`, in that it leaves the whole call context. However, unlike `throw` it is normal control flow.
|
||
|
|
||
|
There's something unfortunate about calls like these:
|
||
|
|
||
|
``` c#
|
||
|
M(Foo1(), Foo2() ?? break, int.TryParse(s, out int x) ? x * 10 : continue);
|
||
|
```
|
||
|
|
||
|
You start computing arguments and then you decide not to do the call. Wouldn't it have been better (at least more efficient) to decide before starting to compute the arguments? Maybe, but that also leads to more ceremony and less locality of the code.
|
||
|
|
||
|
## Conclusion
|
||
|
We are somewhat compelled by the `??` examples. They really do read better than using patterns for nullable things.
|
||
|
|
||
|
Let's have someone champion it, and keep it on the radar.
|
||
|
|
||
|
|
||
|
|
||
|
|