csharplang/meetings/2016/LDM-2016-05-10.md
2017-01-31 10:38:26 -08:00

6.3 KiB

C# Language Design Notes for May 10, 2016

In this meeting we took a look at the possibility of adding new kinds of extension members, beyond extension methods.

Extension members

Ever since we added extension methods in C# 3.0, there's been a consistent ask to add a similar feature for other kinds of function members. While "instance methods" are clearly the most important member kind to "extend with" from the outside, it seems arbitrary and limiting not to have e.g. properties, constructors or static members.

Unfortunately, the declaration syntax for extension methods is sort of a local optimum. The static method with the extra this parameter works great for methods: It is low cost, and it comes with an obvious disambiguation syntax. However, this approach doesn't carry over well to other kinds of members. A property with an extra this parameter? And how would extension static members attach to their type - cannot be through a this parameter representing an instance! As we were designing C# 4 we tried very hard to answer these questions and design an "extension everything" feature that was a syntactic extension (no pun intended) of extension methods. We failed, creating a succession of monsters until we gave up.

The other option is to take a step back, and do a different style of design. Most proposals nowadays do this, and the idea actually goes all the way back to debates we had when adding extension methods in the first place: instead of the extension members saying what they extend, maybe they should be grouped into "extensions"; class-like groups of extension methods that all extend the same type. With the enclosing "extension class" doing all the extension, the members can just be declared with their normal syntax:

class Person
{
    public string Name { get; }
    public Person(string name) { Name = name; }
}

extension class Enrollee extends Person
{
    static Dictionary<Person, Professor> enrollees = new ();                                            // static field
    public void Enroll(Professor supervisor) { enrollees[this] = supervisor; }                          // instance method
    public Professor Supervisor => enrollees.TryGetValue(this, out var supervisor) ? supervisor : null; // instance property
    public static ICollection<Person> Students => enrollees.Keys;                                       // static property
    public Person(string name, Professor supervisor) : this(name) { this.Enroll(supervisor); }          // constructor
}

Issue #11159 summarizes the recent proposals along these lines.

This syntactic approach has clear advantages - writing an extension member is as simple as writing a normal member. It does have some challenges, too. One is, what does it compile into? Another is, how do you disambiguate when several extension members are in scope? Those are trivially answered by the current extension methods, but need more elaborate answers here.

Looking at our current BCL libraries, it is clear that there are many places where layering is poor. Essentially, there's a trade off between layering (separating dependencies) and discoverability (offering members on the important types). If you have a file system, you want to offer a constructor overload that takes a file name. Extension constructors (for instance) address this.

Also, extension members allow putting concrete functionality on interfaces, and on specific instances of generic types.

We think that the most useful and straightforward kinds of extension methods to add would be instance and static versions of methods, properties and indexers, as well as static fields. We don't think instance fields make sense - where would they live? The answer to that is at best complicated, and probably not efficient.

Extension constructors would also be useful. There's a bit more of a design space here, depending on whether you want them to be just like instance constructors (which would require the specific extended type to already have a constructor to delegate to), or more like factories (which could be added to interfaces etc. and produce instances of more derived types).

Extension operators are in a bit of a gray zone. Some of them, like arithmetic operators, probably make sense. Equality operators are hard, because everyone's already got one, so the "built-in" would always shadow. Implicit conversion operators are especially problematic, because their invocation doesn't have an associated syntax, so it's very subtle when they are applied, and there's no easy way to choose not to do it.

Implementation-wise, the extension class would probably turn into a static class, and the extension members would probably be represented as static methods, with the most obvious mapping we can find. There's a case for implementing extension properties via "indexed properties" which are supported in IL (and VB), though maybe we'd prefer mapping to something that could be written directly in C#.

The name of the extension would be the name of the static class, and can also be used in disambiguation, for which we would need a syntax. Maybe existing syntax, such as ((Enrollee)person).Supervisor or (person as Enrollee).Supervisor could be used when disambiguating instance members or operators, whereas static members would just be accessed directly off of the generated static class, as in Enrollee.Students.

Arguably, consumption is more important than production. In this case, that is an argument both for and against introducing a new syntactic approach to declaring extension members: On the one hand, it makes it less of a problem if we effectively deprecate the existing syntax, on the other it makes it less compelling to add syntax for it, instead of maybe some attribute-based approach, or other less syntax-heavy solutions.

For extension properties, people often point to Enumerable.Count(IEnumerable src) as an example of why they want them, but that's a terrible example, because it isn't efficient. Even so, there's probably a good need for these - the Roslyn code base for instance has many little extension methods on top of interfaces, that "should" have been properties. Kind() is a perfect example.

Conclusion

We think this is worth pursuing further. We will come back to it in future design meetings to start ironing out the details.