Finish more notes

This commit is contained in:
Mads Torgersen 2017-04-05 17:19:42 -07:00
parent 0c22b8338f
commit a7b879e006
2 changed files with 89 additions and 40 deletions

View file

@ -6,9 +6,9 @@ Quote of the Day: "Now `(T)default` means the same as `default(T)`!"
We continued to flesh out the designs for features currently considered for C# 7.1.
1. Default expressions
2. Field target on auto-properties
3. private protected
1. Default expressions (*design adopted*)
2. Field target on auto-properties (*yes*)
3. private protected (*yes, if things work as expected*)
# Default expressions
@ -49,17 +49,17 @@ if (e is 0) ... // 0 means (E)0, not (int)0;
For `null`, this means that the null value created by the constant pattern is of the reference or nullable value type of the left hand side. A `null` pattern is not allowed if the type of the left hand side is a non-nullable value type, or a type parameter not known to be a reference type.
As a special dispensation, however, `null` *is* allowed as a constant pattern even if the type of the left hand side is not one that allows constants.
As a special dispensation, however, `null` *is* allowed as a constant pattern even if the type of the left hand side is not one that allows constants.
We will allow `default` as a constant pattern when the type of the matched expression is a reference type, a nullable value type or a constant type.
### case default
Also, `default` cannot be used as a constant pattern:
As a special case (no pun intended), this decision means that it would be permitted to write `case default:` in a switch statement! That is surely going to be confused with a `default:` label, and is almost certainly either a bug or a very bad idea. For this particular syntactic pattern, therefore, we should yield a warning. The warning goes away if you parenthesize `case (default):` or add a type `case default(T):` or similar.
``` c#
if (i is default) ... // error
```
## Optional parameters
On the other hand, expressions of the form `default(T)` are explicitly allowed as default arguments of optional parameters, and therefore, so is `default`:
Expressions of the form `default(T)` are explicitly allowed as default arguments of optional parameters even when non-constant. Therefore, so is `default`:
``` c#
void M(MyStruct ms = default) { ... } // sure
@ -67,55 +67,95 @@ void M(MyStruct ms = default) { ... } // sure
## Special forms
## Syntax
Today `null` is specially allowed in a number of situations, where it is treated as a value even though it is not given a type. We have to decide whether `default` is similarly allowed in those places. Our general position is "no": While `null` conceptually has a specific value, even though its representation varies, `default` conceptually has different values depending on context. Therefore it doesn't make sense to treat it as a value in typeless contexts.
We allow `null == null`, should we allow `default == default`?
Specifics listed out below.
`as` and `is`: work for null.
### Equality
Question: When should I use default, when should I use null? Style wars?
We allow `null == null` as a special case, called out specifically in the language spec. We don't think we should allow `default == default`. Even though it would probably be true regardless of the type, it doesn't make sense with no type.
Can't use `null` as the syntax for this, as it would allow `null` to convert to `int`, which would be breaking.
### is-expressions
We could also disallow `default`, when the type is known to be a reference type.
`null` is allowed on the left hand side of `is` expressions:
Let's not restrict it in the language. It would be a matter of style, and the IDE might give you options about it. The default would probably be `null` when we can, `default` otherwise, and `default(T)` when we have to.
``` c#
bool b = null is T; // allowed, always false
```
"Now `case default:` is allowed!" :-) warning?
Should `default` similarly be allowed? It is important to note that the type `T` in these expressions is *not* a context type for the expression: the compiler does *not* convert the expression to the type. For instance, when you write
Resolution on not-typed syntax that works for null: don't. No `throw default`, no `default == default`, etc.
``` c#
if (0 is DateTime) { ... }
```
`default as long` still disallowed, because the rhs can't be a non-nullable value type.
`default as string` allowed
The result is always *false* (the compiler even warns about it), even though 0 converts to the enum `DateTime`. This is because `0` is interpreted on its own, not through a literal conversion, and on its own it has the type `int`. The `int` zero is not a `DateTime`, so the result is false. It is essentially as if the expression was boxed to `object`, and *then* checked at runtime against the type.
`x is default` is not allowed when `x is default(T)` would have been allowed, where `T` is the type of `x`. That's because we check whether the rhs is a constant, *before* its conversion.
By the same reasoning, if we allowed `default` on the left hand side it would not mean `default(T)` but `default(object)`. That would surely be confusing to the programmer:
`default is T` should not be allowed. It would not mean the same as `default(T) is T`, but more like `default(object) is T`, which is always false.
``` c#
if (0 is int) { ... } // True: 0 is an int
if (default(int) is int) { ... } // True: 0 is still an int
if (default is int) { ... } // Would be false! default(object) is null, which is not an int.
```
"Now `(T)default` means the same as `default(T)`!"
For this reason, we will not allow `default` on the left hand side of an is-expression.
### as-expressions
`null` is allowed on the left hand side of `as` expressions:
``` c#
T t = null as T; // allowed, always null
```
Should `default` similarly be allowed? In case of `as`, the type is restricted to be a reference type or a nullable value type. Therefore `default` always meaning `null` wouldn't be surprising in the same way that it would be for the `is` operator.
In this particular case, therefore, it seems alright to allow `default`, essentially as an alias for `null`.
### throw statements and expressions
You are allowed to `throw null` in C#. It leads to a runtime error, which ironically causes a `NullReferenceException` to be thrown. So it is a sneaky/ugly idiom for throwing a `NullReferenceException`.
There's no good reason to allow this for `default`. We don't think the user has any intuition that that should work, or about what it does.
## Using `default` versus `null`
When should I use `default`, and when should I use `null`? Are we setting up for style wars?
One proposal to circumvent this issue is to not adopt the `default` syntax, but instead use `null` - even for non-nullable value types. Not only would this be confusing, it would also be breaking: It would allow `null` to convert to e.g. `int`, which would affect overload resolution.
Another idea is to allow `default` *only* when the type is not known to be a reference or nullable value type. So the two would be mutually exclusive, and you'd never have a choice.
Let's not restrict it in the language. It would be a matter of style, and an IDE might even give you code style options about it. The recommendation would probably be `null` when we can, `default` otherwise, and `default(T)` when we have to. But you can imagine for instance using `default` for nullable things for uniformity, where a swath of code initializes multiple things, some nullable and some non-nullable. Not allowing that seems unnecessarily restrictive.
## Conclusion
Adopt `default` expressions with the design decisions described above, targeting C# 7.1.
# Field target on auto-properties
[github.com/dotnet/csharplang/issues/42](https://github.com/dotnet/csharplang/issues/42)
Through several releases we have neglected to make the field target on attributes work right when applied to auto-implemented properties. It should attach the attribute to the underlying generated field, just as it does when applied to field-like events.
There are extremely esoteric ways in which this can be a breaking change; essentially when someone exploits unintended leniency in today's compiler. There is absolutely no benefit from such exploitation, and we don't imagine anyone would rely on it. If it turns out we're wrong, we can reintroduce the leniency specifically for the breaking case, but we don't think it is worth it here.
## Conclusion
Adopt the feature with a current target of C# 7.1.
# Private protected
# Field target
We have a complete feature design and a very nearly complete implementation. What we need in order to go forward is to test out a lot of things:
We like this, the design is great, we are good with the drawbacks. If the break turns out to be significant (it won't), we can loosen the specific case.
- Do completion, refactorings, etc. work in IDEs?
- How does it work with languages that don't understand it, including F# and older versions of C#
- How much would it confuse other tools such as CCI?
- Does this fully and completely work as expected in the runtime?
Even though the feature is already allowed in C++/CLI, there's reason to suspect it wasn't exercised all that broadly, so there may be corner cases that don't work as expected.
# private protected
Let's really assure ourselves that this works in the runtime!
What will F# do? They need to understand the metadata even if they can't "see" the members
Lots of tools might get confused. CCI?
It was in C++/CLI but that may not have exercised enough.
Needs to go into C# and VB.
Fair amount in the IDE - test intellisense works out, refactorings etc. Keyword recommendations, etc.
This feature would need to be added to both C# and VB for API parity reasons. We would need to do the work to implement it in VB.
## Conclusion
If all those things work out well enough, we do want the feature.

View file

@ -67,3 +67,12 @@ We went over the proposal for `ref readonly`: [Champion "Readonly ref"](https://
1. Shapes and extensions (*exploration*)
2. Conditional refs (*original design adopted*)
# Mar 7, 2017
[C# Language Design Notes for Mar 7, 2017](LDM-2017-03-07.md)
We continued to flesh out the designs for features currently considered for C# 7.1.
1. Default expressions (*design adopted*)
2. Field target on auto-properties (*yes*)
3. private protected (*yes, if things work as expected*)