通过扩展方法给IFoo.Foo()一个实现
本文关键字:一个 实现 Foo 扩展 方法 IFoo | 更新日期: 2023-09-27 17:50:07
我遇到了以下代码:
public interface IFoo { }
让IFoo
通过扩展方法做某事:
public static FooExtensions
{
public static string Foo(this IFoo foo, string bar)
{
// Do work
return bar;
}
}
这是个好主意吗?为什么不使用带有虚拟Foo()
的抽象类呢?IFoo
可以有一些契约方法,但消费者也可以得到Foo()
的扩展方法。
我的问题是:什么时候像这样的东西是一个好主意?
扩展方法不"使" IFoo
做任何事情。扩展方法只是让你扩展一个封闭的类型…它通常最好与你无法修改的代码一起使用,比如框架类型或第三方类型。
另一种可能性是,如果你有很多逻辑在你的接口的所有实现中完全相同,并且你希望你的接口的消费者不必使用基类型就可以访问该功能。想想LINQ——它是通过扩展方法实现的,你只要实现IEnumerable
就能获得它的所有好处。
在这种情况下,除了不必要的间接层之外,您没有获得任何东西。如果IFoo
应该有能力做Foo
,添加Foo
到接口。
当您不想或不能更改要扩展的类的实现时,扩展方法是一个好主意。IFoo
可以在第三方库中声明。或者可能有很多代码依赖于它,因此很难将其重制为抽象类(可能一些反射依赖于接口)。
一般来说,从用法的角度来看,当扩展方法看起来比老式的静态方法更具可读性时,应该使用扩展方法,无论如何,您都应该使用静态方法而不是新的类成员。当考虑扩展方法vs成员时,在helper类vs成员中考虑静态方法,如果选择static,则考虑将其作为扩展实现是否更好。
但是我经常看到在不需要扩展方法的情况下使用扩展方法,并且通常会使代码可读性降低。所以我不建议使用它们,当它很容易和明显如何避免它们。
什么时候这是个好主意?
当你需要教已经存在的成员实现这个接口的新技巧,像这个从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();
}
你可能想要这样做的原因是当你想要一个接口提供一个方法,而这个方法的实现总是可以使用接口中的其他方法和属性来完成。
接口(与抽象基类不同)无法为方法提供"默认"实现。通过使用扩展方法,您可以提供这样的方法,而无需接口的所有实现者都必须提供相同的重复实现代码。
然而,这种方法的一个主要缺点是扩展方法中的方法是有效密封的——您不能以不同的方式实现它。有时这是可以的,有时不是- YMMV。
另一种方法如下:
- 像往常一样指定你的接口,但要添加有问题的方法。
- 提供一个抽象基类,它为所讨论的方法提供默认代码。
- 当你想提供这个接口的实现时,从抽象基类派生。
您可能想要使用扩展方法的另一个原因是当您无法更改现有接口(例如,因为它是第三方的)或当您不想更改时(因为它会破坏现有代码)。
扩展方法仅仅是语法糖,允许您将fun(t, x)
更改为t.fun(x)
。它们对于发现(智能感知)很有用,或者当你想要组成流畅的函数管道,遵循"更直观"的从左到右风格,而不是从右到左。例如f(x).g(y).h(z)
vs . h(g(f(x),y),z)
。
当你想给任何实现该接口的对象这个实现时,这是一个好主意,不管它是什么实现。
抽象类只向其派生类提供该实现。
如果该接口是您的,或者您有一个实现该接口的基本抽象类,并且可以安全地假设在您的代码中没有非派生自该类的实现-在该抽象类中实现该功能将是一个好主意(但是,您必须强制转换到该抽象类,以使用该方法,这使得接口在某种程度上是多余的)。
但是,如果您想为实现该接口的所有类型提供(该方法的)实现,而不考虑它们的实际实现,那么使用扩展方法将是一个更好的主意。此外,一个类只能从单个类派生——这意味着,通过从抽象类派生,您不能从任何其他类派生。因此,如果你有多个实现该接口的继承链,唯一的解决方案是通过扩展(尽管有其他解决方案来提供功能,但它不会是直接的:objWhichImplIFoo.Foo()
)来(直接)提供该方法给所有的接口,没有重复的代码)。
顺便说一句,还有另一个原因需要一个扩展:如果你想从null
s调用它。如果对象为空,声明的方法总是会抛出NullReferenceException
。因为扩展实际上是静态方法——它们可以在null时调用:
IFoo foo = null;
var something = foo.GetSomethingOrDefault();