A C# Feature Request: Extension Classes
↩ ↪April 10, 2008
As you may have noticed, I’m a big fan of extension methods in C# 3.0, but they aren’t without their limitations. The first one you notice when you use them is the lack of extension properties. Since I like properties more than I like extension methods, this really bummed me out. So for kicks, I’d like to propose a C# feature request: Extension classes.
What’s an extension class?
An extension class is a class that adds methods, properties, etc. to another
class. Given some class Foo
:
public class Foo
{
public int Value { get { return 3; } }
}
My proposed syntax is:
public class FooExtensions this Foo
{
public int DoubledValue
{
get { return Value * 2; }
}
public void Hi()
{
Console.WriteLine("hi.");
}
}
The magic bit up there is the this Foo
. That basically says, “the this
reference inside this class is actually of this other type Foo
”. As you can
see in the implementation of DoubledValue
, it’s accessing the Value
property, which is defined in Foo
.
So, this solves the syntax problem for how to define an extension property. It makes sense, too, because if you’ve used extension methods, you’ve noticed that you always have to define them inside a “special” extension class anyway. This just moves the “extension-ness” indicator from the method level up to the containing class level.
What it’s not
Just to be clear, this isn’t the same as inheriting, partial classes, or open
classes. There is no way to have a reference of type FooExtensions
, because
that class is not directly instantiable. It’s just decorating another class.
Likewise, FooExtensions
does not have priveleged access to Foo
. It can
only use public members just like outside code. And, of course, FooExtensions
can’t replace any methods in Foo. No open classes.
And then?
OK, so we’ve got properties in the mix, is there anything else we could add?
public class FooExtensions this Foo
{
public static SomeMethod() { /* ... */ }
}
How about static methods and properties? You could say there’s no value in doing this since you have to access statics through the class name anyway, but I disagree.
A design guideline that’s been floating around in C++ land for a long
time is “prefer non-member functions over member functions”. The
idea is that coupling and priveleged access to a class are bad, so if you can
implement something outside of the class, that’s better. However, if doing that
changes the calling convention, you’ve affected all your call sites with what
should be an implementation detail. I don’t want users to have to know that
SomeMethod()
was implemented outside of Foo
. That way, if I later need to
move it into Foo
(or vice versa), they aren’t affected.
Where this is going
So, if you can move more and more out of Foo
, what’s left in it? The
answer: data and validation. Foo
itself is the only class that can have
fields. In my world, the way I’d like to design classes is like this: the core
class Foo
contains its fields and provides accessors and mutators to prevent
it from being put in a bad state. Foo
basically is state and state change.
Almost everything else, the “things you can do with a Foo
” would be pulled out
into extension classes, likely in different namespaces. If you want to format a
Foo
for display, UI.FooExtensions
will have methods for that. Need to
serialize it? Serialization.FooExtensions
.
Aside from being crazy about decoupling, there’s some more reasoning behind this. It turns out that state is actually one of the trickiest parts of programming. Computer scientists have been wrestling with its implications since the dawn of programming, and have tried all sorts of ways to get around it, up to the point of trying to abolish it completely (sort of) in “pure” functional languages.
Now the lack of real-world use of languages like that kind of hints that state is inevitable. One of the most useful things about computers is that they remember things. However, isolating and minimizing where and how they remember things, I think, will encourage us to write software that’s easier to reason about and maintain.