csharplang/meetings/2014/LDM-2014-02-10.md

77 lines
6 KiB
Markdown
Raw Normal View History

# 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 its coming out, its 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
Well 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 well go with the simple rule: A namespace completely shadows a type of the same name, and you cant import the members of such a type. If this turns out to be a problem were 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 its just what you want.
#### Conclusion
Well 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 wouldnt 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 arent 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 wasnt `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 dont always run, so its 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 theyre doing and initializers wouldnt make anything worse. However, initializers would only run if the user-defined constructors dont chain to the default constructor with `this()`.
### Conclusion
Lets 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 dont 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 theres nothing reasonable we can do when we dont 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. Lets prohibit and revisit if it becomes a problem.