【问题标题】:Giving IFoo.Foo() an implementation via extension method通过扩展方法给 IFoo.Foo() 一个实现
【发布时间】:2014-04-18 03:26:10
【问题描述】:
我遇到了以下代码:
public interface IFoo { }
通过扩展方法让IFoo做某事:
public static FooExtensions
{
public static string Foo(this IFoo foo, string bar)
{
// Do work
return bar;
}
}
这是个好主意吗?为什么不使用带有虚拟Foo() 的抽象类呢? IFoo 可能有一些契约方法,但消费者也可以获得 Foo() 扩展方法。
我的问题是:什么时候是个好主意?
【问题讨论】:
标签:
c#
class
interface
extension-methods
【解决方案1】:
扩展方法不会“使”IFoo 做任何事情。扩展方法只是让您扩展一个封闭的类型...通常最好与您无法修改的代码结合使用,例如框架类型或第三方类型。
另一种可能性是,如果您的接口的所有实现都有很多完全相同的逻辑,并且您希望接口的使用者无需使用基本类型即可访问该功能。想想 LINQ——它是通过扩展方法实现的,您只需实现 IEnumerable 即可获得它的所有好处。
在这种情况下,除了不必要的间接层之外,您没有获得任何东西。如果IFoo应该有能力做Foo,把Foo添加到界面。
【解决方案2】:
当您不想或不能更改您正在扩展的类的实现时,扩展方法是一个好主意。 IFoo 可以在第三方库中声明。或者可能有很多代码依赖于它,因此很难将其重新制作为抽象类(可能某些反射依赖于接口)。
一般而言,从使用的角度来看,当扩展方法看起来比老式静态方法更具可读性时,您应该使用扩展方法,并且无论如何您都应该使用静态方法而不是新的类成员。在考虑扩展方法 vs 成员时,考虑辅助类中的静态方法 vs member 和 如果你选择 static,那么考虑是否最好将其实现为扩展。
但我经常看到在确实不需要的情况下使用扩展方法,并且通常会降低代码的可读性。所以我不建议在如何避免它们容易且明显时使用它们。
【解决方案3】:
什么时候是这样的好主意?
当您需要用新技巧来教已经实现此接口的现有成员时,例如 System.Core 程序集中的这个:
// System.Linq.Enumerable
public static TSource First<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
if (source == null)
{
throw Error.ArgumentNull("source");
}
if (predicate == null)
{
throw Error.ArgumentNull("predicate");
}
foreach (TSource current in source)
{
if (predicate(current))
{
return current;
}
}
throw Error.NoMatch();
}
【解决方案4】:
您可能希望这样做的原因是,当您希望接口提供方法并且该方法的实现始终可以使用接口中的其他方法和属性来完成时。
接口(与抽象基类不同)无法为方法提供“默认”实现。通过使用扩展方法,您可以提供这样的方法,而无需接口的所有实现者都必须提供相同的重复实现代码。
但是,这种方法的一个主要缺点是扩展方法中的方法被有效地密封——你不能以不同的方式实现它。有时可以,有时不行 - YMMV。
另一种方法如下:
- 像往常一样指定您的界面,但将相关方法添加到其中。
- 提供一个抽象基类,为相关方法提供默认代码。
- 当您想要提供此接口的实现时,从抽象基类派生。
您可能想要使用扩展方法的另一个原因是当您无法更改现有接口(例如,因为它是第三方)或不想更改时(因为它会破坏现有代码)。
【解决方案5】:
扩展方法只是语法糖,它允许您将fun(t, x) 更改为t.fun(x)。它们对于发现(智能感知)很有用,或者当您想要组成流畅的函数管道时,这些管道遵循“更直观”的从左到右风格,而不是从右到左。例如f(x).g(y).h(z) 与h(g(f(x),y),z)。
除了混乱的智能感知之外,使用它们并没有任何缺点。
【解决方案6】:
当您想将此实现提供给实现该接口的任何对象时,这是一个好主意,无论那是什么实现。
抽象类仅向其派生类提供该实现。
如果该接口是您的,或者您有一个实现该接口的基本抽象类,并且可以安全地假设您的代码中没有任何不是从该类派生的实现 - 这将是一个在该抽象类中实现该功能是个好主意(但是,您必须强制转换为该抽象类才能使用该方法,这使得接口在某种程度上是多余的)。
但是,如果您想为实现该接口的所有类型提供(该方法的)实现,而不管它们的实际实现如何 - 扩展方法将是一个更好的主意。
此外,一个类只能从一个类派生 - 这意味着通过从该抽象类派生,您不能从任何其他类派生。因此,如果您将拥有多个实现该接口的继承链,那么(直接)向所有继承链提供该方法而不重复代码的唯一解决方案是通过扩展(尽管还有其他解决方案来提供该功能,但是它不会是直接的:objWhichImplIFoo.Foo())。
顺便说一句,想要扩展还有另一个原因:如果您希望它可以从nulls 调用。如果对象为 null,则声明的方法将始终抛出 NullReferenceException。因为扩展实际上是静态方法——它们可以在 null 上调用:
IFoo foo = null;
var something = foo.GetSomethingOrDefault();