76 lines
6 KiB
Markdown
76 lines
6 KiB
Markdown
# C# Design Notes for Feb 10, 2014
|
||
|
||
## Agenda
|
||
1. Design of using static <_design adopted_>
|
||
2. Initializers in structs <_allow in certain situations_>
|
||
3. Null-propagation and unconstrained generics <_keep current design_>
|
||
|
||
## Design of using static
|
||
The “using static” feature was added in some form to the Roslyn codebase years ago, and sat there quietly waiting for us to decide whether to add it to the language. Now it’s coming out, it’s time to ensure it has the right design.
|
||
### Syntax
|
||
Should the feature have different syntax from namespace usings, or should it be just like that, but just specifying a type instead? The downside of keeping the current syntax is that we need to deal with ambiguities between types and namespaces with the same name. That seems relatively rare, though, and sticking with current syntax definitely makes it feel more baked in:
|
||
``` c#
|
||
using System.Console;
|
||
```
|
||
as opposed to, e.g.:
|
||
``` c#
|
||
using static System.Console;
|
||
```
|
||
#### Conclusion
|
||
We’ll stick with the current syntax.
|
||
|
||
### Ambiguities
|
||
This leads to the question of how to handle ambiguities when there are both namespaces and types of a given name. We clearly need to prefer namespaces over types for compatibility reasons. The question is whether we make a choice at the point of the specified name, or whether we allow “overlaying” the type and the namespace, disambiguating at the next level down by preferring names that came from the namespace over ones from the type.
|
||
|
||
#### Conclusion
|
||
We think overlaps are sufficiently rare that we’ll go with the simple rule: A namespace completely shadows a type of the same name, and you can’t import the members of such a type. If this turns out to be a problem we’re free to loosen it up later.
|
||
|
||
### Which types can you import?
|
||
Static classes, all classes, enums? It seems it is almost always a mistake to import non-static types: they will have names that are designed to be used with the type name, such as `Create`, `FromArray`, `Empty`, etc., that are likely to appear meaningless on their own, and clash with others. Enums are more of a gray area. Spilling the enum members to top-level would often be bad, and could very easily lead to massive name clashes, but sometimes it’s just what you want.
|
||
|
||
#### Conclusion
|
||
We’ll disallow both enums and non-static classes for now.
|
||
|
||
### Nested types
|
||
Should nested types be imported as top-level names?
|
||
|
||
#### Conclusion
|
||
Sure, why not? They are often used by the very members that are being “spilled”, so it makes sense that they are spilled also.
|
||
|
||
### Extension methods
|
||
Should extension methods be imported as extension methods? As ordinary static methods? When we first introduced extension methods, a lot of people asked for a more granular way of applying them. This could be it: get the extension methods just from a single class instead of the whole namespace. For instance:
|
||
``` c#
|
||
using System.Linq.Enumerable;
|
||
```
|
||
Would import just the query methods for in-memory collections, not those for `IQueryable<T>`.
|
||
On the other hand, extension methods are designed to be used as such: you only call them as static methods to disambiguate. So it seems wrong if they are allowed to pollute the top-level namespace as static methods. On the _other_ other hand, this would be the first place in the language where an extension method wouldn’t be treated like a static method.
|
||
|
||
#### Conclusion
|
||
We will import extension methods as extension methods, but not as static methods. This seems to hit the best usability point.
|
||
|
||
## Initializers in structs
|
||
Currently, field initializers aren’t allowed in structs. The reason is that initializers _look_ like they will be executed every time the struct is created, whereas that would not be the case: If the struct wasn’t `new`’ed, or it was `new`’ed with the default constructor, no user defined code would run. People who put initializers on fields might not be aware that they don’t always run, so it’s better to prevent them.
|
||
|
||
It would be nice to have the benefits of primary constructors on structs, but that only really flies if the struct can make use of the parameters in scope through initializers. Also, we now have initializers for auto-properties, making the issue worse. What to do?
|
||
|
||
We can never prevent people from having uninitialized structs, and the struct type authors still need to make sure that an uninitialized struct is meaningful. However, if a struct has user-defined constructors, chances are they know what they’re doing and initializers wouldn’t make anything worse. However, initializers would only run if the user-defined constructors don’t chain to the default constructor with `this()`.
|
||
|
||
### Conclusion
|
||
Let’s allow field and property initializers in structs, but only if there is a user-defined constructor (explicit or primary) that does not chain to the default constructor. If people want to initialize with the default constructor first, they should call it from their constructor, rather than chain to it.
|
||
``` c#
|
||
struct S0 { public int x = 5; } // Bad
|
||
struct S1 { public int x = 5; S1(int i) : this() { x += i; } } // Bad
|
||
struct S2 { public int x = 5; S2(int i) { this = new S2(); x += i; } } // Good
|
||
struct S3(int i) { public int x = 5 + i; } // Good
|
||
```
|
||
## Unconstrained generics in null-propagating operator
|
||
We previously looked at a problem with the null-propagating operator, where if the member accessed is of an unconstrained generic type, we don’t know how to generate the result, and what its type should be:
|
||
``` c#
|
||
var result = x?.Y;
|
||
```
|
||
The answer is different when `Y` is instantiated with reference types, non-nullable value types and nullable value types, so there’s nothing reasonable we can do when we don’t know which.
|
||
The proposal has been raised to fall back to type `object`, and generate code that boxes (which is a harmless operation for values that are already of reference type).
|
||
|
||
### Conclusion
|
||
This seems like a hack. While usable in some cases, it is weirdly different from the mainline semantics of the feature. Let’s prohibit and revisit if it becomes a problem.
|
||
|