通过扩展方法给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.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。

另一种方法如下:

  1. 像往常一样指定你的接口,但要添加有问题的方法。
  2. 提供一个抽象基类,它为所讨论的方法提供默认代码。
  3. 当你想提供这个接口的实现时,从抽象基类派生。

您可能想要使用扩展方法的另一个原因是当您无法更改现有接口(例如,因为它是第三方的)或当您不想更改时(因为它会破坏现有代码)。

扩展方法仅仅是语法糖,允许您将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();