views:

285

answers:

6

I find them a very natural way to extend existing classes, especially when you just need to "spot-weld" some functionality onto an existing class.

Microsoft says, "In general, we recommend that you implement extension methods sparingly and only when you have to." And yet extension methods form the foundation of Linq; in fact, Linq was the reason extension methods were created.

Are there specific design criteria where using extension methods are perferred over inheritance or composition? Under what criteria are they discouraged?

+8  A: 

The problem is that Extension methods aren't necessarily clear.

When you see something like this in code:

 myObject.Foo();

The natural instinct is that Foo() is a method defined on myObject's class, or a parent class of myObject's class.

With Extension methods, that's just not true. The method could be defined nearly ANYWHERE, for nearly any type (you can define an extension method on System.Object, although it's a bad idea...). This really increases the maintenance cost of your code, since it reduces discoverability.

Any time you add code that is non-obvious, or even reduces the "obviousness" of the implementation, there is a cost involved in terms of long term maintainability.

Reed Copsey
Is it that hard to right click and select "Go to Definition"?
Mongus Pong
@Mongus: No, it's not hard. - but you still are probably mistaken on where it is defined in the hierarchy. Also, if you have extension methods with the same name as instance methods, it adds confusion. There are many reasons to avoid them - even if they are often useful.
Reed Copsey
+3  A: 

Excessive use of extension methods is bad for the same reason that excessive use of overloaded operators is bad. Let me explain.

Let's look at the operator overloading example first. When you see the code:

var result = myFoo + myBar;

you would like to hope that myFoo and myBar are numeric types, and the operator + is performing addition. This is logical, intuitive, easy to understand. But once operator overloading enters the picture, you can no longer be sure of what myFoo + myBar is actually doing - the + operator could be overloaded to mean anything. You can't just read the code and figure out what's happening without having to read all of the code underlying the types involved in the expression. Now, operator + is already overloaded for types like String or DateTime - however, there is an intuitive interpretation to what addition means in those cases. More importantly, in those common cases it adds a lot of expressive power to the code. So it's worth the potential confusion.

So what does all this have to do with extension methods? Well, extension methods introduce a similar situation. Without extension methods, when you see:

var result = myFoo.DoSomething();

you can assume that DoSomething() is either a method of myFoo or one of it's base classes. This is simple, easy to understand, intuitive even. But with extension methods, DoSomething() could be defined anywhere - and worse, the definition depends on the set of using statements in the code file and bring in potentially many classes into the mix, any one of which could host the implementation of DoSomething().

Now don't get me wrong. Both operator overloading and extension methods are useful and powerful language features. But remember - with great power comes great responsibility. You should use these features when they improve the clarity or capability of the implementation. If you start using them indiscriminately, they will add confusion, complexity, and possibly defects to what you are trying to create.

LBushkin
+1 for quoting Uncle Ben.
jkohlhepp
+1  A: 

Extension methods can be thought of as Aspect Oriented. I believe Microsoft is saying, if you have the source code, you should be altering the original class. It has to do with clarity and ease of maintenance. The reason extension methods were needed is that they extend functionality of existing objects. In the case of LINQ, if you dig into how it works, objects can be created with a variety of types that cannot be known ahead of time. Using extension methods allows some behaviors to be added.

For example, it makes sense to extend .NET framework objects because you don't have the source, but may have some behavior to add to an object. Extending the string, DateTime and FrameworkElement classes is acceptable. It may also be acceptable to create extension methods for classes that cross application boundaries. Say, if you wanted to share a DTO class and have some specific UI behaviour, create that extension method in the UI only.

With dynamic languages, the paradigm is different, but I believe you're talking about C# and VB.net.

greglev
+8  A: 

We show proposed new language features (at least the ones that have a halfway decent chance of seeing the light of day) to the C# MVPs for early feedback. Some feedback we get very often on many features is "well I would use this feature judiciously and in accordance with the design principles of the feature, but my goofball coworkers are going to go crazy nuts with this thing and write up a mass of unmaintainable, non-understandable code that I'm going to get stuck with debugging for the next ten years, so as much as I want it, please never implement this feature!"

Though I am exaggerating somewhat for comedic effect, this is something we take very seriously; we want the design of new features to encourage their use but discourage their misuse.

We worried that extension methods would be used to, for example, indiscriminantly extend "object" arbitrarily and thereby create big balls of loosely typed mud that would be hard to understand, debug and maintain.

I'm personally in particular concerned about "cute" extension methods that you see in "fluent" programming. Stuff like

6.Times(x=>Print("hello"));

Yuck. "for" loops are universally understandable and idiomatic in C#; don't get cute.

Eric Lippert
As ironically as this is, that extension method might get placed right next to my `.ForEach<T>` method! I find that easily understandable and much less verbose than the for loop, also it would make it impossible to accidentally introduce the off by 1 error using i < count vs i <= count.
Chris Marisic
@Chris: In that case, just use foreach. Eric's excellent post here explains the issues with doing ForEach<T> - http://blogs.msdn.com/ericlippert/archive/2009/05/18/foreach-vs-foreach.aspx
Reed Copsey
And yet, F# includes `Seq.iter` which is exactly equivalent to an `IEnumerable<T>.ForEach` extension method. I know, different language, different design philosophy, but the argument about producing side effects could still be applied, and F# is a lot closer to being functionally "pure" than C# is.
Joel Mueller
+2  A: 

I follow a very simple rule with extension methods and helper classes.

Anytime I have a method that could be relegated to a static helper class I will weigh the usefulness of it in a general scope, do I really want this method to be shared? Is the meaning clear and useful to others?

If I can answer yes to this question I will create it to be an extension method. Where I have a shared library that among other things contains all of my Extension methods in 1 namespace with each class file defined as TypeNameExtension so it becomes very clear what to expect is inside that class so that you can easily locate the code.

If I question the need for a helper method as a globally accessible usage I will declare the method private static and leave it inside the owning class.

Chris Marisic
+3  A: 

I have a growing library of extension methods that operate on BCL classes for simplifying common problems, that look confusing at first glance. Frankly, I think Extension Methods make things a lot more readable. For instance, take an if statement testing to see if a number is between two numbers:

if (x <= right && x >= left )
{
   // Do Something
}

What if right is less than left on accident? And exactly what does that do? When the variables are simply like x and left, then its easy to undertand. But what if they are properties on a long class? The if statement can get pretty big, which obfuscates the rather simple thing you are trying to do.

So I wrote this:

public static bool Between<T>(this T test, T minValue, T maxValue)
    where T : IComparable<T>
{
    // If minValue is greater than maxValue, simply reverse the comparision.
    if (minValue.CompareTo(maxValue) == 1)
        return (test.CompareTo(maxValue) >= 0 && test.CompareTo(minValue) <= 0);
    else
        return (test.CompareTo(minValue) >= 0 && test.CompareTo(maxValue) <= 0);
}

Now I can improve my if statement into this:

if (x.Between(left, right))
{
   // Do Something
}

Is this "extraordinary"? I don't think so... but I do think it's a significant improvement on readability, and it allows some extra checking on the test inputs for safety.

The danger I think Microsoft wants to avoid here is people writing a ton of Extension Methods that redefine Equals and ToString.

Nick
But is it really better than if (Between(test, minv, maxv)) {} ?
jmucchiello
@jmucchiello - Actually, it would be `if (MathUtilities.Between(test, minv, maxv)) {}`. So you would have to ask yourself, what value did having `MathUtilities` give me, and did it take away from the readability? I think the answer is yes. That's the whole point behind Fluent interfaces.
Nick
@jmucchiello it certainly reads better and is clearer as to what is going to be between what..
Mongus Pong
@Nick: To play devil's advocate, I think this is really odd, and perfectly valid with your syntax: "Bar".IsBetween("Foo", "Baz"); - By specifying MathUtils, I at least know I'm doing something weird...
Reed Copsey
@Reed - Why is that odd? If you can sort a list of strings and make them alphabetically ordered, then can you really say that another string being between two strings in a list is odd? If something can be ordered, then having something be between two of those things naturally follows.
Nick
@Nick: Well, I would not associate anything in MathUtilities with a string - for one, which was where you obviously wrote this. String comparisons typically require extra care, though, for casing and culture related issues.
Reed Copsey
@Reed - Granted... but isn't that an issue for how `string` implements `IComparable`? Wouldn't that issue with casing and anything else also affect sorting?
Nick
@Reed - BTW, this actually one of the reasons why it really bothers me that there is no `INumeric` interface that explicitly defines something as being a number, with mathematical operations on it. There are lots of generic methods that are only appropriate for numeric types, but there is no good way to constrain for that.
Nick
@Nick: Yes, I wish there was an IArithmetic interface as well - but there isn't, unfortunately. Have you tried MiscUtils? http://www.yoda.arachsys.com/csharp/miscutil/ It does much of this...
Reed Copsey
@Reed - Yep... I'm well aware of it. Still, I think IArithmetic/INumeric is a pretty basic thing... one of the things I was hoping to see in .NET 4. I guess there's always v5.
Nick
@Nick - Actually, I forgot C# doesn't have bare bones functions like C++ does. So in my world there would be no "MathUtilities."
jmucchiello