开发者

Prefer extension methods for encapsulation and reusability?

开发者 https://www.devze.com 2023-01-03 21:44 出处:网络
edit4: wikified, since this seems to have morphed more into a discussion than a specific question. In C++ programming, it\'s generally considered good practice to "prefer non-member non-friend fu

edit4: wikified, since this seems to have morphed more into a discussion than a specific question.

In C++ programming, it's generally considered good practice to "prefer non-member non-friend functions" instead of instance methods. This has been recommended by Scott Meyers in this classic Dr. Dobbs article, and repeated by Herb Sutter and Andrei Alexandrescu in C++ Coding Standards (item 44); the general argument being that if a function can do its job solely by relying on the public interface exposed by the class, it actually increases encapsulation to have it be external. While this confuses the "packaging" of the class to some extent, the benefits are generally considered worth it.

Now, ever since I've started programming in C#, I've had a feeling that here is the ultimate expression of the concept that they're trying to achieve with "non-member, non-friend functions that are part of a class interface". C# adds two crucial components to the mix - the first being interfaces, and the second extension methods:

  • Interfaces allow a class to formally specify their public contract, the methods and properties that they're exposing to the world.
  • Any other class can choose to implement the same interface and fulfill that same contract.
  • Extension methods can be defined on an interface, providing any functionality that can be implemented via the interface to all implementers automatically.
  • And best of all, because of the "instance syntax" sugar and IDE support, they can be called the same way as any other instance method, eliminating the cognitive overhead!

So you get the encapsulation benefits of "non-member, non-friend" functions with the convenience of members. Seems like the best of both worlds to me; the .NET library itself providing a shining example in LINQ. However, everywhere I look I see people warning against extension method overuse; even the MSDN page itself states:

In general, we recommend that you implement extension methods sparingly and only when you have to.

(edit: Even in the current .NET library, I can see places where it would've been useful to have extensions instead of instance methods - for example, all of the utility functions of List<T> (Sort, BinarySearch, FindIndex, etc.) would be incredibly useful if they were lifted up to IList<T> - getting free bonus functionality like that adds a lot more benefit to implementing the interface.)

So what's the verdict? Are extension methods the acme of encapsulation and code reuse, or am I just deluding myself?

(edit2: In response to Tomas - while C# did start out with Java's (overly, imo) OO mentality, it seems to be embracing more multi-paradigm programming with every new release; the main thrust of this question is whether using extension methods to drive a style change (towards more generic / functional C#) is useful or worthwhile..)

edit3: overridable extension methods

The only real problem identified so far with this approach, is that you can't specialize extension methods if you need to. I've been thinking about the issue, and I think I've come up with a solution.

Suppose I have an interface MyInterface, which I want to extend -

I define my extension methods in a MyExtension static class, and pair it with another interface, call it MyExtensionOverrider. MyExtension methods are defined according to this pattern:

public static int MyMethod(this MyInterface obj, int arg, bool attemptCast=true)
{
    if (attemptCast && obj is MyExtensionOverrider)
    {
        return ((MyExtensionOverrider)obj).MyMethod(arg);
    }
    // regular implementation here
}

The override interface mirrors all of the methods defined in MyExtension, except without the this or attemptCast parameters:

public interface MyExtensionOverrider
{
    int MyMethod(int arg);
    string MyOtherMethod();
}

Now, any class can implement the interface and get the default extension functionality:

public class MyClass : MyInterface { ... }

Anyone that wants to override it with specific implementations can additionally implement the override interface:

public class MySpecializedClass : MyInterface, MyExtensionOverrider
{
    public int MyMethod(int arg) 
    { 
        //specialized implementation for one method
    }
    public string MyOtherMethod() 
    {   // fallback to default for others
        MyExtension.MyOtherMethod(this, attemptCast: false); 
    }
}

And there we go: extension methods provided on an interface, with the option of complete extensibility if needed. Fully general too, the interface itself doesn't need to know about the extension / override, and multiple extension / override pairs can be implemented without interfering with each other.

I can see three problems with this approach -

  1. It's a little bit fragile - the extension methods and override interface have to be kept synchronized manually.
  2. It's a little bit ugly - implementing the override interface involves boilerplate for every function you don't开发者_Python百科 want to specialize.
  3. It's a little bit slow - there's an extra bool comparison and cast attempt added to the mainline of every method.

Still, all those notwithstanding, I think this is the best we can get until there's language support for interface functions. Thoughts?


I think that C# follows slightly different logic - just like in Java, the axiom in C# is that everything is an object and all functionality should be encapsulated in the class (as methods). C# isn't as strict - there are value types that aren't really object and there are static members, which also don't belong to any object.

Extension methods add one capability that wasn't possible before - you can add members to interfaces (implemented in terms of the core members of the interface). This is great and very useful, but I think it should be used only when adding member to an interface is what you need (just like the use in LINQ).

One possible problem with prefering extension methods over instance methods is that you may later realize that you actually need to use some private state in the method - then you would have to change extension method into an instance method (which breaks binary compatibility) or expose some private information...

It would be definitely usable to distinguish between members that rely on private state directly and "derived" members that are implemented in terms of public operations, but I don't think that extension methods are that great for this. Perhaps it would be possible to mark such methods with some attribute (e.g. UsesOnlyPublic) and write some FxCop rule to make sure that the method doesn't violate the policy...


I generally like extension methods, particularly on interfaces, but I have two issues with them:

First, if an implementation has a more efficient way of achieving the extension method's purpose, there's no general way of expressing that. For example, Enumerable.Count() explicitly knows about ICollection/ICollection<T> and special-cases it. An alternative for this would be if interfaces could actually contain implementations directly, referencing only other interface methods and not declaring fields. The methods could then be overridden in appropriate implementations. This does mean you need to own the interface, of course... but in some cases it would be cleaner than current extension methods. (By avoiding the ability to introduce fields, I believe you get round some implementation problems which multiple inheritance of classes would introduce.)

Second, I don't like the way extension methods are discovered. There's no way to say, "I want the extension methods from class X" without also dragging in the extension methods from other classes in the same namespace. I would like you to be able to write:

using static System.Linq.Enumerable;

to pick up only those extension methods.

(Incidentally, I'll be talking more about both of these points at NDC 2010 on Thursday. Hopefully the talk will be recorded.)

The ability to specify general algorithms which only rely on the public interface is nice. The ability to call those algorithms on the type providing the interface is nice. The current mechanism just has a few sharp corners.

Incidentally, it might be quite nice to be able to write methods within a type but say, "Limit me to only using the public API."


Extension methods do seem to directly address the encapsulation principle that you cite. But I do see danger in their overuse. Without this language feature, you have basically two options for implementing your "non-member non-friend" functions that complement a given interface: static utility functions or a wrapper/proxy class.

I think the problem with the extension method approach is that it's basically a convenient syntactic construct around static utility functions. It just provides you with method call semantics for a static utility function.

But in the absence of extension methods I think most would agree that wrapper/proxy classes are a better way to implement your "non-member non-friend" functions. I think that this really just comes down to the organic growth of your codebase in an object-oriented way. The class that you make today as a simple wrapper/proxy may grow into a first class component in your system tomorrow. It's a real class, so it can have members of its own and potentially grow in scope along with your expanding use cases.

So I think extension methods have the danger of encouraging the proliferation of what are basically static utility functions at the expense of classes and objects, which are the constructs that you most want to cultivate in your codebase.

0

精彩评论

暂无评论...
验证码 换一张
取 消