csharplang/meetings/2014/LDM-2014-02-10.md
2017-02-09 09:25:09 -08:00

6 KiB
Raw Permalink Blame 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:

using System.Console;

as opposed to, e.g.:

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:

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 newed, or it was newed 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.

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:

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.