C# 继承与多态入门






4.64/5 (129投票s)
使用简单的代码片段,对 C# 中的继承和多态进行基础介绍
引言
本文档适合初次尝试 C# 编程的 .NET 新手。我假设他们已经进行过一些基础的 C++ 编程,并且了解类和成员函数。通过一些简单的代码片段,我们将了解 C# 如何支持继承和多态。
继承与多态
当您从基类派生一个类时,派生类将继承基类的所有成员(构造函数除外),但派生类是否能够访问这些成员取决于这些成员在基类中的可访问性。C# 通过继承为我们提供了多态。基于继承的多态允许我们在基类中定义方法,并在派生类中重写它们。因此,如果您有一个基类对象,它可能持有多个派生类对象中的一个,那么正确使用多态可以使您调用一个方法,该方法会根据对象所属的派生类类型而产生不同的行为。
考虑以下我们将用作基类的类。
class Animal
{
public Animal()
{
Console.WriteLine("Animal constructor");
}
public void Greet()
{
Console.WriteLine("Animal says Hello");
}
public void Talk()
{
Console.WriteLine("Animal talk");
}
public virtual void Sing()
{
Console.WriteLine("Animal song");
}
};
现在看看我们如何从这个基类派生另一个类。
class Dog : Animal
{
public Dog()
{
Console.WriteLine("Dog constructor");
}
public new void Talk()
{
Console.WriteLine("Dog talk");
}
public override void Sing()
{
Console.WriteLine("Dog song");
}
};
现在尝试运行这段代码。
Animal a1 = new Animal();
a1.Talk();
a1.Sing();
a1.Greet();
//Output
Animal constructor
Animal talk
Animal song
Animal says Hello
好的,结果如预期。现在尝试运行这段代码。
Animal a2 = new Dog();
a2.Talk();
a2.Sing();
a2.Greet();
//Output
Animal constructor
Dog constructor
Animal talk
Dog song
Animal says Hello
我们有一个 `Animal` 类型的对象,但它引用了一个 `Dog` 类型的对象。因此,您可以看到首先调用了基类构造函数,然后调用了派生类构造函数。现在我们调用 `Talk()`,发现执行的方法是基类的方法。考虑到对象被声明为基类型(在本例中为 `Animal`),这并不令人意外。现在当我们调用 `Sing()` 时,我们发现调用的是派生类的方法。这是因为在基类中,该方法被声明为 `public virtual void Sing()`,而在派生类中,我们使用 `public override void Sing()` 来重写它。在 C# 中,我们需要显式使用 `override` 关键字,这与 C++ 不同,在 C++ 中则不需要。最后,当我们调用 `Greet()` 时,调用的是基类的方法,这并不令人费解,尤其是因为派生类甚至没有实现该方法。
现在尝试运行以下代码。
Dog d1 = new Dog();
d1.Talk();
d1.Sing();
d1.Greet();
//Output
Animal constructor
Dog constructor
Dog talk
Dog song
Animal says Hello
好的,这里一切都如预期。没有意外。我们能够调用 `Greet()` 方法证明了 C# 中的继承,尽管我想一开始没有人怀疑这一点。现在看看我们将用作其他类基类的这个新类。
class Color
{
public virtual void Fill()
{
Console.WriteLine("Fill me up with color");
}
public void Fill(string s)
{
Console.WriteLine("Fill me up with {0}",s);
}
};
现在运行这段代码。
Color c1 = new Color();
c1.Fill();
c1.Fill("red");
//Output
Fill me up with color
Fill me up with red
好的,进展顺利,我想。现在让我们从这个类派生一个类。
class Green : Color
{
public override void Fill()
{
Console.WriteLine("Fill me up with green");
}
};
现在尝试运行这段代码。
Green g1 = new Green();
g1.Fill();
g1.Fill("violet");
//Output
Fill me up with green
Fill me up with violet
嗯,这也进展顺利。因此,如果您有重载的方法,您可以将其中一些标记为 `virtual`,并在派生类中重写它们。不要求您必须重写所有重载。现在我想演示一些关于重载构造函数的内容。为此,我们将使用以下基类。
class Software
{
public Software()
{
m_x = 100;
}
public Software(int y)
{
m_x = y;
}
protected int m_x;
};
现在我们将从上述类派生一个类。
class MicrosoftSoftware : Software
{
public MicrosoftSoftware()
{
Console.WriteLine(m_x);
}
};
现在尝试运行这段代码
MicrosoftSoftware m1 = new MicrosoftSoftware();
//MicrosoftSoftware m2 = new MicrosoftSoftware(300); //won't compile
//Output
100
基类有两个重载的构造函数。一个接受零个参数,另一个接受一个 `int`。在派生类中,我们只有一个零参数构造函数。构造函数不会被派生类继承。因此,我们无法使用接受 `int` 作为参数的构造函数来实例化派生类对象。正如您从输出中推断出的,调用的是默认的无参构造函数。现在看看这个第二个派生类。
class DundasSoftware : Software
{
//Here I am telling the compiler which
//overload of the base constructor to call
public DundasSoftware(int y) : base(y)
{
Console.WriteLine(m_x);
}
//Here we are telling the compiler to first
//call the other overload of the constructor
public DundasSoftware(string s, int f) : this(f)
{
Console.WriteLine(s);
}
};
这里我们有两个构造函数,一个接受 `int`,另一个接受 `string` 和 `int`。现在让我们尝试一些代码。
DundasSoftware du1 = new DundasSoftware(50);
//Output
50
DundasSoftware du2 = new DundasSoftware("test",75);
//Output
75
test
好了,现在您已经看到了结果,我想事情变得更清楚了。您可以使用 `this` 和 `base` 访问关键字来处理其他方法,而不仅仅是构造函数。
结论
本文档不讨论接口。至少在其当前版本中不讨论。我可能会在下次更新中添加接口的用法。但目前,我建议您从其他地方阅读有关接口的内容。
历史
- 2002 年 7 月 9 日 - 文章已完全重做,并添加了示例项目。
- 2001 年 10 月 10 日 - 发布文章 [我在 CP 的第一篇文章]