tza*_*man 15 c# c++ extension-methods encapsulation
edit4:wikified ,因为这似乎已经变成了讨论而不是特定的问题.
在C++编程中,通常认为"更喜欢非成员非朋友函数"而不是实例方法是一种好习惯.这是Scott Meyers在这篇经典的Dr. Dobbs文章中推荐的,Herb Sutter和Andrei Alexandrescu在C++编码标准中重复了这一点(第44项); 一般的论点是,如果一个函数只能通过依赖类暴露的公共接口来完成它的工作,它实际上增加了封装,使其成为外部的.虽然这在某种程度上混淆了班级的"包装",但通常认为其好处是值得的.
现在,自从我开始用C#编程以来,我有一种感觉,这就是他们试图通过"非成员,非朋友的函数来实现这个概念的最终表达"接口".C#为混合添加了两个关键组件 - 第一个是接口,第二个是扩展方法:
因此,您可以通过会员的便利获得"非会员,非朋友"功能的封装优势.对我来说似乎是两全其美的事情; .NET库本身在LINQ中提供了一个光辉的例子.然而,我看到的每个地方都看到人们警告不要过度使用扩展方法; 甚至MSDN页面本身都说明:
通常,我们建议您谨慎实施扩展方法,并且只在必要时才实施.
(编辑:即使在目前的.NET库,我能看到它会一直有扩展,而不是实例方法有用的地方-例如,所有的实用功能List<T>(Sort,BinarySearch,FindIndex等)将是非常有用的如果他们被提升到IList<T>- 获得这样的免费奖励功能可以为实现界面带来更多好处.)
那么判决是什么?扩展方法是封装和代码重用的极致,还是我只是在欺骗自己?
(编辑2 :对Tomas的回应 - 虽然C#确实从Java(过度,imo)OO心态开始,但似乎每个新版本都采用了更多的多范式编程;这个问题的主旨是使用扩展方法来推动风格改变(朝向更通用/功能性的C#)是有用或有价值的.)
到目前为止,使用此方法确定的唯一真正问题是,如果需要,您无法专门使用扩展方法.我一直在考虑这个问题,我想我已经找到了解决方案.
假设我有一个接口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
}
Run Code Online (Sandbox Code Playgroud)
覆盖接口镜像了所有定义的方法MyExtension,除了没有this或attemptCast参数:
public interface MyExtensionOverrider
{
int MyMethod(int arg);
string MyOtherMethod();
}
Run Code Online (Sandbox Code Playgroud)
现在,任何类都可以实现接口并获得默认扩展功能:
public class MyClass : MyInterface { ... }
Run Code Online (Sandbox Code Playgroud)
任何想要使用特定实现覆盖它的人都可以另外实现覆盖接口:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
我们继续:在接口上提供扩展方法,如果需要,可以选择完全可扩展性.完全一般,接口本身不需要知道扩展/覆盖,并且可以实现多个扩展/覆盖对而不会相互干扰.
我可以看到这种方法存在三个问题 -
bool比较和强制转换尝试添加到每个方法的主线.尽管如此,我认为这是我们能够获得的最好的东西,直到接口函数的语言支持.思考?
我认为C#遵循略微不同的逻辑 - 就像在Java中一样,C#中的公理是一切都是对象,所有功能都应该封装在类中(作为方法).C#不是那么严格 - 有些值不是真正的对象,并且有静态成员,它们也不属于任何对象.
扩展方法添加了以前无法实现的功能 - 您可以向接口添加成员(根据接口的核心成员实现).这很棒且非常有用,但我认为只有在向接口添加成员时才应该使用它(就像在LINQ中使用一样).
在实例方法中使用扩展方法的一个可能的问题是,您可能稍后意识到您实际上需要在方法中使用某个私有状态 - 然后您必须将扩展方法更改为实例方法(这会破坏二进制兼容性)或暴露一些私人信息......
区分直接依赖私有状态的成员和用公共操作实现的"派生"成员绝对可以用,但我不认为扩展方法对此很有用.也许有可能用某些属性标记这样的方法(例如UsesOnlyPublic)并编写一些FxCop规则以确保该方法不违反策略......
我通常喜欢扩展方法,尤其是接口上的扩展方法,但我对它们有两个问题:
首先,如果实现有更有效的方法来实现扩展方法的目的,则没有通用的方法来表达它。例如,Enumerable.Count() 明确了解ICollection/ICollection<T>及其特殊情况。另一种方法是接口实际上可以直接包含实现,仅引用其他接口方法而不声明字段。然后可以在适当的实现中重写这些方法。当然,这确实意味着您需要拥有该接口......但在某些情况下它会比当前的扩展方法更干净。(通过避免引入字段的能力,我相信您可以解决类的多重继承会引入的一些实现问题。)
其次,我不喜欢扩展方法的发现方式。如果不拖入同一命名空间中其他类的扩展方法,就无法说“我想要类 X 的扩展方法”。我希望你能够写:
using static System.Linq.Enumerable;
Run Code Online (Sandbox Code Playgroud)
仅选择那些扩展方法。
(顺便说一句,我将在周四的NDC 2010上更多地讨论这两点。希望这次演讲能够被记录下来。)
指定仅依赖于公共接口的通用算法的能力很好。在提供接口的类型上调用这些算法的能力很好。目前的机制只有一些尖角。
顺便说一句,能够在类型中编写方法可能会非常好,但要说“限制我只使用公共 API”。
| 归档时间: |
|
| 查看次数: |
1572 次 |
| 最近记录: |