【发布时间】:2011-03-03 12:13:03
【问题描述】:
edit4: 已被维基化,因为这似乎更多地演变为讨论而不是具体问题。
在 C++ 编程中,“首选非成员非友元函数”而不是实例方法通常被认为是一种好的做法。这已由 Scott Meyers 在this classic Dr. Dobbs article 中推荐,并由 Herb Sutter 和 Andrei Alexandrescu 在C++ Coding Standards 中重复(第 44 项);一般的论点是,如果一个函数可以仅仅依靠类公开的公共接口来完成它的工作,它实际上增加了封装使其成为外部的。虽然这在某种程度上混淆了类的“包装”,但通常认为这些好处是值得的。
现在,自从我开始使用 C# 编程以来,我就有一种感觉,here 是他们试图实现的概念的终极表达使用“作为类接口一部分的非成员、非友元函数”来实现。 C# 添加了两个关键组件 - 第一个是 interfaces,第二个是 extension methods:
- 接口允许类正式指定其公共契约、它们向世界公开的方法和属性。
- 任何其他类都可以选择实现相同的接口并履行相同的契约。
- 扩展方法可以在接口上定义,向所有实现者自动提供可以通过接口实现的任何功能。
- 最重要的是,由于“实例语法”糖和 IDE 支持,它们可以像任何其他实例方法一样以相同的方式调用,从而消除认知开销!
这样你就可以在会员方便的同时获得“非会员、非朋友”功能的封装好处。对我来说似乎是两全其美; .NET 库本身在 LINQ 中提供了一个光辉的例子。然而,无论我看到哪里,我都看到人们警告不要过度使用扩展方法;甚至 MSDN 页面本身也声明:
一般来说,我们建议您谨慎地实施扩展方法,并且仅在必要时才实施。
(edit: 即使在当前的 .NET 库中,我也可以看到使用扩展而不是实例方法有用的地方——例如,@ 的所有实用函数如果将 987654326@(Sort、BinarySearch、FindIndex 等)提升到IList<T>,将会非常有用 - 获得这样的免费奖励功能为实现接口增加了更多好处。)
那么判决结果是什么?扩展方法是封装和代码重用的极致,还是我在自欺欺人?
(edit2: 作为对 Tomas 的回应——虽然 C# 确实从 Java(过度 imo)的 OO 心态开始,但它似乎在每个新版本中都包含更多的多范式编程;主要这个问题的主旨是使用扩展方法来推动风格改变(朝向更通用/功能性 C#)是否有用或值得..)
edit3:可覆盖的扩展方法
到目前为止,这种方法发现的唯一真正问题是,如果需要,您不能专门化扩展方法。我一直在思考这个问题,我想我已经想出了一个解决方案。
假设我有一个接口MyInterface,我想扩展它-
我在MyExtension 静态类中定义我的扩展方法,并将其与另一个接口配对,称为MyExtensionOverrider。 MyExtension 方法是按照这种模式定义的:
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
}
覆盖接口镜像MyExtension中定义的所有方法,除了没有this或attemptCast参数:
public interface MyExtensionOverrider
{
int MyMethod(int arg);
string MyOtherMethod();
}
现在,任何类都可以实现接口并获得默认的扩展功能:
public class MyClass : MyInterface { ... }
任何想用特定实现覆盖它的人都可以另外实现覆盖接口:
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);
}
}
我们开始了:接口上提供的扩展方法,如果需要,可以选择完全可扩展性。也完全通用,接口本身不需要知道扩展/覆盖,并且可以实现多个扩展/覆盖对而不会相互干扰。
我可以看到这种方法存在三个问题 -
- 有点脆弱 - 扩展方法和覆盖接口必须手动保持同步。
- 有点难看 - 实现覆盖接口涉及到您不想想要专门化的每个函数的样板。
- 有点慢 - 每个方法的主线都添加了额外的
bool比较和强制转换尝试。
尽管如此,我认为这是我们能得到的最好的,直到接口函数的语言支持。想法?
【问题讨论】:
-
当你说 C# 添加接口时,我觉得 C++ 没有。 C++(主要)通过纯虚函数拥有它们。
-
如果一个扩展方法如此重要以至于他需要出现在基类的所有实现者中,为什么不首先在基类中呢?
-
@SB:抽象基类不是完全相同的东西。 @ereOn:因为封装最好不允许访问类内部,除非您绝对必须这样做。阅读我在问题中链接的第一篇文章。
-
@ereOn,如果扩展方法是基类上的方法,如何在不重写整个 .Net Framework 和 CLR 的情况下让 LINQ 工作?
-
Joe Duffy 的一篇有趣的文章展示了这种方法的一些优缺点:bluebytesoftware.com/blog/2010/02/10/…
标签: c# c++ extension-methods encapsulation