【发布时间】:2010-12-01 05:57:58
【问题描述】:
我对 C# 中抽象类的使用有点困惑。在 C++ 中,定义继承抽象类的类可以遵循的模板是有意义的。但是,在 C# 中,接口不是起到同样的作用吗?
确实,抽象类可以具有接口不提供的默认实现。所以如果实现不需要包含在基类中,是不是更好的接口?
【问题讨论】:
标签: c#
我对 C# 中抽象类的使用有点困惑。在 C++ 中,定义继承抽象类的类可以遵循的模板是有意义的。但是,在 C# 中,接口不是起到同样的作用吗?
确实,抽象类可以具有接口不提供的默认实现。所以如果实现不需要包含在基类中,是不是更好的接口?
【问题讨论】:
标签: c#
我仍然喜欢提供一个接口的默认抽象实现,假设它是一个实体接口(而且它是有意义的)。你永远不知道什么时候可以向接口添加一些具有简单默认实现的东西,可以包含并“免费”提供给从抽象基类继承的任何人。
【讨论】:
using (VB Includes) 声明中,并且不能访问任何非公共成员。扩展方法让您可以更轻松地定义功能,否则您可能会在类的外部复制这些功能。
这个CodeProject article 有很多关于两者之间差异的信息,包括一个比较和对比每个特征的表格。
接口定义了类之间的契约——类相互调用的方式。一个类可以实现多个接口,但只能从一个抽象类继承。
【讨论】:
确实,抽象类可以具有接口不提供的默认实现。因此,如果不需要将实现包含在基类中,是否最好选择接口?
是的:)。如果在基类中实现某些对 all 继承类通用的方法是有意义的,则应该使用抽象类。如果基类只用于定义接口,而继承的类之间没有通用逻辑,则使用接口。
【讨论】:
接口和抽象类服务于不同的目标。接口用于声明类的契约,而抽象类用于共享公共实现。
如果你只使用抽象类,你的类不能从其他类继承,因为 C# 不支持多重继承。如果你只使用接口,你的类不能共享公共代码。
public interface IFoo
{
void Bar();
}
public abstract class FooBase : IFoo
{
public abstract void Bar()
{
// Do some stuff usually required for IFoo.
}
}
现在我们可以在各种情况下使用接口和基础实现了。
public class FooOne : FooBase
{
public override void Bar()
{
base.Bar(); // Use base implementation.
// Do specialized stuff.
}
}
public class FooTwo : FooBase
{
public override void Bar()
{
// Do other specialized stuff.
base.Bar(); // Use base implementation.
// Do more specialized stuff.
}
}
// This class cannot use the base implementation from FooBase because
// of inheriting from OtherClass but it can still implement IFoo.
public class FooThree : OtherClass, IFoo
{
public virtual void Bar()
{
// Do stuff.
}
}
【讨论】:
第一个问题,是的。
对于你的第二个答案,我会给你一些我遵循的提示。
使用抽象类
当创建一个将被广泛分布或重用的类库时——尤其是对于客户端,使用抽象类而不是接口;因为,它简化了版本控制。
使用抽象类为类型族定义公共基类。
使用抽象类提供默认行为。
仅继承该类逻辑所属层次结构中的基类。
使用界面
创建可随意更改的独立项目时,使用接口优先于抽象类;因为,它提供了更多的设计灵活性。
使用接口引入多态行为而无需子类化,并为多重继承建模——允许特定类型支持多种行为。
使用接口为值类型设计多态层次结构。
在真正需要不可变合约时使用接口。
设计良好的界面定义了非常具体的功能范围。拆分包含不相关功能的接口。
【讨论】:
您可以实现任意数量的接口,但只能继承一个类。所以类和接口在 C# 中是完全不同的野兽,你不能互换使用它们。在 C# 中,抽象类仍然是类,而不是接口。
【讨论】:
如果您没有任何默认/通用代码,请使用接口。
抽象类也可以作为模板,定义一些算法的步骤和调用顺序,派生类提供这些步骤的实现:
public abstract class Processor
{
// this is the only public method
// implements the order of the separate steps
public void Process()
{
Step1();
Step2();
//...
}
// implementation is provided by derived classes
protected abstract void Step1();
protected abstract void Step2();
}
【讨论】:
虽然没有实现的抽象类确实等同于接口,但接口和抽象类用于不同的事物。
接口可用于最一般意义上的多态性。例如,ICollection 用于定义所有集合的接口(有不少)。在这里,它定义了您要对某种类型执行的操作。还有许多其他用途(例如可测试性、依赖注入等)。此外,界面可以混合使用,这在概念上和技术上都有效。
抽象类更多地与可模板化的行为有关,其中虚拟方法是“填补空白”的地方。显然你不能混合抽象类(至少在 C# 中不能)。
【讨论】:
在 C# 中,使用抽象类的一大障碍是只能使用一个。使用接口,您的优点是不限制实现的基类。为此,我总是使用接口,即使我创建了一个抽象基类来帮助实现。
基本抽象类的另一个烦恼通常是它们倾向于依赖模板参数。这会使您的其余代码很难使用。解决这个问题的简单方法是提供一个与抽象类对话的接口,而无需知道模板类的类型参数。
其他人似乎输入他们的答案更快,但请允许我总结一下......
使用接口。如果需要共享实现,还可以创建一个抽象基类,提供通用的实现细节。
【讨论】:
请注意,使用 C#3,您可以通过使用扩展方法为接口提供默认行为。但是,有一些限制,抽象类仍然有其一席之地。
【讨论】:
我在建模时遵循的规则是: 类(包括抽象)和结构模型实体。接口模型行为。 实现接口的实体可以被视为展示接口(合同)公开的行为。
【讨论】:
这在一些答案中有所暗示,但没有明确说明。
您可以实现多个接口并且只能从一个基类继承,就好像它们是同一枚硬币的两个面一样,这不是一个好方法。
不要将接口视为对象层次结构的一部分。它们通常只是您的真实对象层次结构可以声明为实现的功能的一小部分(或者至少是特定的,如果不是很小的话)。以 IDisposable 为例。如果你是写这个的人,你会问自己它应该是一个抽象类还是一个接口?很明显,在这种情况下,它们是两个完全不同的东西。我想成为一次性的。想想 ICloneable 和 IEnumerable。你可以在你的类中实现这些,而不必尝试让你的类派生自一些不相关的类,如 List 或 Array。或采取 IEnumerator。只需为对象提供 MoveNext 类型的视图。我的类可以提供该功能,而不必笨拙地从与我的类无关的其他顺序集合数据类型派生。
【讨论】:
我总是更喜欢接口,只要基类没有一些真正“繁重”的实现,这将为实现者节省大量时间。 给那个 .net 只允许一个基类继承,强迫你的用户继承是一个巨大的限制。
【讨论】:
你应该总是更喜欢编程而不是接口而不是具体的类。
如果您还想要一个默认实现,您仍然可以创建一个基类来实现您的接口。
【讨论】: