(1 item) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(6 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(4 items) |
|
(2 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(5 items) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(8 items) |
|
(2 items) |
|
(7 items) |
|
(2 items) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(3 items) |
|
(2 items) |
|
(2 items) |
|
(8 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(6 items) |
|
(1 item) |
|
(2 items) |
|
(5 items) |
|
(5 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(16 items) |
|
(10 items) |
|
(27 items) |
|
(15 items) |
|
(15 items) |
|
(13 items) |
|
(16 items) |
|
(15 items) |
One of the new C# 3.0 language features is 'extension methods'. The basic idea is that the set of methods available on an instance of a particular type is open to extension. In effect, we can add new methods to existing types.
Some people seem to think that this is stupid because, they claim, you can do the same thing with inheritance. However, the inheritance approach is rife with problems. Let's look at an example.
Supposing you are dealing with some type
Foo
that you don't own - maybe
it's part of a 3rd party class library, so you
don't get to modify the class. Suppose that this Foo
class doesn't define a
method called Bar
. However, let's say you've written a method Bar
that does
something useful with a Foo
instance. You might decide you'd like to be able to use the following syntax to invoke your Bar
method:
void UseFoo(Foo f)
{
f.Bar();
}
As an aside, my first reaction to this is always: why exactly do you want to do that? I tend to think that if you've
written your own method that's not a part of Foo
but which does stuff to Foo
, then
the code should probably reflect that. I think it's more honest, and easier for someone else to read if your
code looks like this:
void UseFoo(Foo f)
{
Bar(f);
}
However, requests for the former style seem to come up often enough that there's no denying the fact that some people really want to do it that way, apparently. It's not to my taste, but it takes all sorts.
Some people would use inheritance to 'solve' this. The inheritance approach looks like this:
public class MyBastardizedFoo : Foo { public void Bar() { ... } }
You can probably guess how I feel about this from the class name. Why do I dislike it?
Well for starters there's the problem that it often doesn't work. Look at the first UseFoo
above.
Where is that Foo
instance coming from? If we've been passed a Foo
by code we don't control,
then the fact that we've derived our own class from Foo
doesn't help, because we've been passed an
instance of the base class. If we try to cast to our derived class at this point, we'll just cause an exception. The inheritance approach can
only be made to work in those cases where you get to create the object in the first
place, which severely restricts its utility. (Sealed classes also get in the way. But even if all
classes were unsealed, an idea that seems to appeal to some people, you're still left with
the problem that you often don't get to create the instance.)
I also have a philosophical objection to using inheritance here. Even in the subset of scenarios where you get create the relevant object and can therefore make inheritance work I think it's the wrong thing to do. Specifically, if only reason you're using inheritance is the economy of the syntax in the first example, I believe that inheritance is the wrong way to go. That's because I think there's an important distinction between an operation that an object does, and some operation that is done with object where the object is a passive party.
Look at the two UseFoo
methods above. They suggest different things. The first suggests that
the Foo
is going to do something because I just told it to. The second suggests that
some action is being performed that needs to make use of the Foo
, but which won't be performed
by the Foo
. When there's a choice of two different syntaxes that suggest
two different things, I think it's best to pick the one that best represents what's actually
happening.
The canonical example of getting this wrong can be found in
the .NET Framework's
String
class. Time and
time again I've seen beginners get confused by this:
void UseString(string s) { s.ToUpper(); ... }
Seasoned .NET developers will see the problem instantly, but only because they've learned to spot it. (Actually, particularly seasoned developers will notice two problems. But getting into invariant cultures at this point would be a digression too far, even for me.)
Developers new to .NET tend to be surprised that calling ToUpper
on a string
doesn't convert it to upper case. And I think it's perfectly reasonably to be surprised. ToUpper
looks
like an imperative operation. Its form actively conceals the fact that it's really a function in the
strict functional programming sense - it's has no side effects, it merely takes an input expression returns
a result based on that input expression without modifying the input in any way. The input expression is the implicit this
reference - the object on which ToUpper
is invoked.
I think this form would have been more honest, and less likely to mislead:
String.ToUpper(input)
It's true that this would be more cumbersome, and would require more typing than
what we've got today. But misleading code seems like a high price to pay for
a little less typing - aren't we always being told that code will be read far more
often than it is written? And even if you are convinced by the argument that a core
class like String
is sufficiently widely used that (a) it's worth the
savings in this case because it affects everyone, and (b) everyone knows String
so it doesn't matter that it's a special case, it still seems like the wrong thing
to do in the general case.
I'm a big fan of code that does what it looks like it does. So if the first UseFoo
above is
a more accurate representation of what's happening than the second, you should use the first even if it means
more typing. And of course if the second genuinely makes more sense, then by all means use it. But I suspect that the second style is wrong
more often than it's right, not least because all of the examples I've seen put forward
as arguments for making String
unsealed would actually make more
sense with the first syntax. Nonetheless,
I'm sure there are some cases where the second version is the right one, in which case go for it! Except of course in C# 1 and 2.0 you often
can't use the second one, because either you don't control the creation of the object in question, or the type in question
happens to be sealed.
Which brings us back to the fact that inheritance isn't the right solution here.
Dynamic language advocates will sometimes chip in at this point to claim that they have a 'better' solution. In Python you can dynamically add new methods to an instance:
def UseFoo(f): f.Bar = SomeMethod f.Bar()
Better my arse.
This does circumvent the "I didn't create the object" and the "the class is sealed" problems. So it's better in the sense that it gets the job done. But it
suffers from being bleedin' 'orrible, which tends to put me off. Why's it horrible? It changes the instance
that was passed in. Was my caller expecting me to edit the set of methods available on that instance? What if the
caller was using this self same trick to add a Bar
method of their own? We just broke their code.
It seems pretty gross to me to go modifying someone
else's object just for the syntactic convenience of the method I'm writing right
now.
Extension methods offer an alternative solution for the scenarios where you really want to use the syntax in the first UseFoo
above. They let you
augment the set of method available on a reference of any type,
even when you have no control over
the type or instance in question. Morever, they do so in a way that can be completely self-contained, which seems a lot cleaner to me than either inheritance
or the dynamic language trick of fiddling with the instance.
Despite being cleaner than the alternatives, I still think the following is pretty unpleasant, but it does illustrate how to write and use an extension method. The
Print
method extends the String
class, which is signified by the extra this
keyword on the first parameter. This method enables us to pretend that variables of type String
have a Print
instance method, which we
take advantage of in the
Main
method.
static class Program { static void Main(string[] args) { string s = "Hello, world"; s.Print(); } static void Print(this string s) { Console.WriteLine(s); } }
OK, so that made me feel slightly dirty, and not in a good way...
That said, it grosses me out less than the dynamic language approach of dynamically
adding new methods
to the string object that was passed in... At least with this
extension method, the unpleasantness is all strictly in the privacy
of my own class. That Print
is private so nobody else has to put up with it.
Just to be clear, this is only an alternate syntax. That line where we call the Print
method on the string
instance is exactly equivalent to the following code:
Print(s);
Extension methods are simply methods that can be called using either style. So looking back at our
original two UseFoo
examples, if Bar
were an extension method you could use
either syntax and it would compile down to the same thing.
While the Print
method here happens to be private, you can also define these things as public
methods on public types. (Both the method and the declaring class must be static
.)
At this point any code with a using
statement bringing in the namespace containing
the types in question will have access to these methods. This is where I start to get a little more
nervous. Let's suppose some joker decides to write this:
namespace System { public static class Evil { public static void Print(this object s) { Console.WriteLine(s); } } }
Since almost all C# source files have a using System;
declaration, this extension method is
effectively going to be available globally - it augments the set
of methods available on the base Object
type. Indeed, Peter Ritchie drew an apt analogy
on the DOTNET-CX list:
"Extension methods seems to be a hack to recover from lack (or removal, considering C++ roots) of nonmember functions"
They certainly feel somewhat similar in usage, although the syntax is rather funkier. The fact that the name of the class that defines the function is nowhere to be seen at the call site (at least not in the source code) does rather suggest that these things really don't want to be class members. And this introduces a couple of problems: (1) how are you meant to discover what extension methods are available to you, and (2) how are you meant to know where a function call will go from looking at the code? The answer to (1) is probably a combination of documentation idioms and IntelliSense. (2) I'm not so sure about.
While I hope it's fairly obvious that defining your own types in the System
namespace or its descendants is a
Bad Thing if you're not Microsoft, the more subtle and worrying
possibility is that class libraries might 'helpfully' put such extension methods into their own top-level
namespaces. For example, if I do a using FooSoft.FooLib;
, I might
get more than just the classes I wanted to use from FooLib
- I might pick up a load of extension
methods I didn't particularly want, but which the authors of FooLib
thought I wouldn't be able to live without. Let's hope everyone follows Microsoft's lead and segragates
these things into separate namespaces - the extension methods that form part of
LINQ are all in System.Query
for example, so you won't get them unless you ask for them. Then again, that's not the only stuf in that namespace
so maybe we're already in trouble...
In practice I suspect I'm being unduly nervous. I think my slightly negative reaction is similar to the
feeling I first got when I saw non-member functions in C++ proposed as being better than member functions in certain
scenarios. "But that's not OO," I spluttered in my (relatively) youthful idealism. These days I've learned to be
suspicious of any practice whose sole justification is that it's "more object-oriented."
If that's the only reason something has been done in a particular way, it's probably
a code smell. OO is just a
toolkit, and for certain jobs, other styles (e.g. functional programming) sometimes offer better tools. Of course, if being "more object-oriented" offers
specific advantages in your scenario, then more power to you, but being OO should
not be an end in itself. The fact is
that not all methods belong in classes.
You can see this from the way some classes are really nothing more than namespaces in disguise.
(Take a look at System.Math
for example.)
C# 3.0 currently only offers extension methods. It does not allow us to bolt properties or events onto existing types. I find this particularly interesting because one of my favourite technologies, WPF (aka Avalon) does the opposite. WPF doesn't offer extension methods, but you can define 'attached' properties and events, which can be attached to other objects. And I'm a big fan of these.
These crop up all over the place in WPF. UI building tends to involve lots of orthogonal concepts. For example, layout is something that you tend to want to apply to all visual elements. A good UI framework should let you introduce new layout managers that can work across all existing element types without needing to rewrite those types. Input is another example. Mouse and keyboard input apply to everything, as does ink. And while you could argue that new input mechanisms don't come along as often as new layout systems, they still appear from time to time. Ink is a relative newcomer. My new laptop has an integrated fingerprint reader, which seems to me like a input device that is very different in character from keys, mouse, pen, voice, or joystick. (Right now its tied to the login UI, but I can imagine scenarios where it would be useful for applications to be able to use it.) Text handling is yet another cross-cutting feature - you want to apply text styling properties to more or less anything, since most controls can end up showing text.
In short, when you're building UIs, you tend to want to be able to plug together
arbitrary combinations of features. This makes single inheritance particularly ill-suited to UI frameworks. Not that it's impossible to devise UI frameworks that use single
inheritance - I think that the Windows Forms does a pretty good job of it, and its
System.Windows.Forms.Control
class is a tour de force as base
classes go. But it's not hugely open to certain kinds of extension. The supported layout styles are baked in to this base class, making it
custom layout managers feel like second class citizens. Microsoft can add new layout styles by adding new features to Control
, but what about my layout manager? Likewise, the set of supported input systems is
prescribed. Windows Forms does what it does well, but it sometimes feels like a strait jacket
once you're used to WPF.
You could use this as an argument for multi inheritance. Certainly MI can enable orthogonal inheritance-based composition. But why bring inheritance into it if all you actually wanted was orthogonal composition? As I've said in the past, one of the great tragedies of the current crop of mainstream languages is the bizarre and awkward construct we call inheritance. It conflates two very important but really rather different concepts: reuse and polymorphism. When you stop and think about it, having one solution that attempts to address these two requirements is a bit bizarre. The last thing we need is a greater emphasis on inheritance, so I'm not about to lobby for MI in .NET. Particularly not now that I've spent some time seeing non-inheritance-based compositional reuse in action, in the form of attached properties and events.
I've already gone on long enough here, and this part is incidental to the main topic, so I won't go into a detailed explanation of attached properties and events. But I can't leave it hanging without an example.
Layout is the first place most people come across attached properties. WPF defines various 'panels' which arrange
their contents in certain ways. These range from the very simple explicit positioning offered by Canvas
,
through simple automated layouts like StackPanel
up to fairly sophisticated
table-style layout with Grid
.
For most layout styles, the elements being arranged may need to pass information to the panel in order to be arranged correctly. Elements on a
Canvas
must specify their exact position. Elements in a Grid
need to indicate where they
appear on the grid coordinate system. Elements in a DockPanel
must indicate which edge of the panel they
wish to use. A simple nesting approach won't always work - children of a grid may choose to span multiple grid cells,
for example.
Children indicate their requirements to the panel through properties. But each panel requires different properties. In a world of attached properties, this isn't a problem - each panel type defines the properties its children will need. E.g.:
Hello!
If I want to introduce a new layout, it will work in exactly the same way as the built-in panels - I can define my own attached properties. No more looking like a second class citizen.
In this case the attached properties are effectively just annotations. They feel a bit like .NET custom attributes here in that they're just bits of information attached to a particular element, and which will be processed by some container rather than by the target element. However, it doesn't have to be this way - it is quite possible to define an attached property which actively does stuff to the target element if you want.
Likewise, events can be attached. An element doesn't need to know of a particular event type in order for that event to be raised on that element. So input devices can route whatever input handling events they see fit through any UI element without needing any help from the element. This makes it possible for new input devices to be introduced as time goes on.
I've come to like attached events and properties more and more as I've become used to them. Maybe in time I'll feel the same way about extension methods.