C# 继承
继承是面向对象程序设计中最重要的概念之一。继承允许我们根据一个类来定义另一个类,这使得创建和维护应用程序变得更容易。同时也有利于重用代码和节省开发时间。
当创建一个类时,程序员不需要完全重新编写新的数据成员和成员函数,只需要设计一个新的类,继承了已有的类的成员即可。这个已有的类被称为的基类,这个新的类被称为派生类。
继承的思想实现了 属于(IS-A) 关系。例如,哺乳动物 属于(IS-A) 动物,狗 属于(IS-A) 哺乳动物,因此狗 属于(IS-A) 动物。
基类和派生类
一个类可以继承自另一个类,被称为基类(父类)和派生类(子类)。
C# 不支持类的多重继承,但支持接口的多重继承,一个类可以实现多个接口。
概括来说:一个类可以继承多个接口,但只能继承自一个类。
C# 中创建派生类的语法如下:
{
...
}
class <派生类> : <基类>
{
...
}
派生类会继承基类的成员(字段、方法、属性等),除非它们被明确地标记为私有(private)。
派生类可以通过关键字base来调用基类的构造函数和方法。
实例
{
public void SomeMethod()
{
// Method implementation
}
}
class DerivedClass : BaseClass
{
public void AnotherMethod()
{
// Accessing base class method
base.SomeMethod();
// Method implementation
}
}
假设,有一个基类 Shape,它的派生类是 Rectangle:
实例
namespace InheritanceApplication
{
class Shape
{
public void setWidth(int w)
{
width = w;
}
public void setHeight(int h)
{
height = h;
}
protected int width;
protected int height;
}
// 派生类
class Rectangle: Shape
{
public int getArea()
{
return (width * height);
}
}
class RectangleTester
{
static void Main(string[] args)
{
Rectangle Rect = new Rectangle();
Rect.setWidth(5);
Rect.setHeight(7);
// 打印对象的面积
Console.WriteLine("总面积: {0}", Rect.getArea());
Console.ReadKey();
}
}
}
当上面的代码被编译和执行时,它会产生下列结果:
总面积: 35
基类的初始化
派生类继承了基类的成员变量和成员方法。因此父类对象应在子类对象创建之前被创建。您可以在成员初始化列表中进行父类的初始化。
下面的程序演示了这点:
实例
namespace RectangleApplication
{
class Rectangle
{
// 成员变量
protected double length;
protected double width;
public Rectangle(double l, double w)
{
length = l;
width = w;
}
public double GetArea()
{
return length * width;
}
public void Display()
{
Console.WriteLine("长度: {0}", length);
Console.WriteLine("宽度: {0}", width);
Console.WriteLine("面积: {0}", GetArea());
}
}//end class Rectangle
class Tabletop : Rectangle
{
private double cost;
public Tabletop(double l, double w) : base(l, w)
{ }
public double GetCost()
{
double cost;
cost = GetArea() * 70;
return cost;
}
public void Display()
{
base.Display();
Console.WriteLine("成本: {0}", GetCost());
}
}
class ExecuteRectangle
{
static void Main(string[] args)
{
Tabletop t = new Tabletop(4.5, 7.5);
t.Display();
Console.ReadLine();
}
}
}
当上面的代码被编译和执行时,它会产生下列结果:
长度: 4.5 宽度: 7.5 面积: 33.75 成本: 2362.5
继承接口(Interface Inheritance)
一个接口可以继承自一个或多个其他接口,派生接口继承了基接口的所有成员。
派生接口可以扩展基接口的成员列表,但不能改变它们的访问修饰符。
实例
{
void Method1();
}
interface IDerivedInterface : IBaseInterface
{
void Method2();
}
继承接口的实例可以通过以下方式来实现:
实例
// 定义一个基接口
interface IBaseInterface
{
void Method1();
}
// 定义一个派生接口,继承自基接口
interface IDerivedInterface : IBaseInterface
{
void Method2();
}
// 实现派生接口的类
class MyClass : IDerivedInterface
{
public void Method1()
{
Console.WriteLine("Method1 implementation");
}
public void Method2()
{
Console.WriteLine("Method2 implementation");
}
}
class Program
{
static void Main(string[] args)
{
// 创建 MyClass 类的实例
MyClass obj = new MyClass();
// 调用继承自基接口的方法
obj.Method1();
// 调用派生接口新增的方法
obj.Method2();
}
}
以上实例中 MyClass 类实现了 IDerivedInterface 接口,因此必须提供 IDerivedInterface 中定义的所有方法,包括从 IBaseInterface继承的 Method1() 方法。 在 Main 方法中,我们创建了 MyClass 的实例 obj 并调用了它的方法。
输出结果为:
Method1 implementation Method2 implementation
C# 多重继承
多重继承指的是一个类别可以同时从多于一个父类继承行为与特征的功能。与单一继承相对,单一继承指一个类别只可以继承自一个父类。
C# 不支持多重继承。但是,您可以使用接口来实现多重继承。下面的程序演示了这点:
实例
namespace InheritanceApplication
{
class Shape
{
public void setWidth(int w)
{
width = w;
}
public void setHeight(int h)
{
height = h;
}
protected int width;
protected int height;
}
// 基类 PaintCost
public interface PaintCost
{
int getCost(int area);
}
// 派生类
class Rectangle : Shape, PaintCost
{
public int getArea()
{
return (width * height);
}
public int getCost(int area)
{
return area * 70;
}
}
class RectangleTester
{
static void Main(string[] args)
{
Rectangle Rect = new Rectangle();
int area;
Rect.setWidth(5);
Rect.setHeight(7);
area = Rect.getArea();
// 打印对象的面积
Console.WriteLine("总面积: {0}", Rect.getArea());
Console.WriteLine("油漆总成本: ${0}" , Rect.getCost(area));
Console.ReadKey();
}
}
}
当上面的代码被编译和执行时,它会产生下列结果:
总面积: 35 油漆总成本: $2450
Jimmy
jia***gpay@outlook.com
参考地址
为什么一个对象可以用父类声明,却用子类实例化
这个实例是子类的,但是因为你声明时是用父类声明的,所以你用正常的办法访问不到子类自己的成员,只能访问到从父类继承来的成员。
在子类中用 override 重写父类中用 virtual 申明的虚方法时,实例化父类调用该方法,执行时调用的是子类中重写的方法;
如果子类中用 new 覆盖父类中用 virtual 申明的虚方法时,实例化父类调用该方法,执行时调用的是父类中的虚方法;
/// <summary> /// 父类 /// </summary> public class ParentClass { public virtual void ParVirMethod() { Console.WriteLine("父类的方法..."); } } /// <summary> /// 子类1 /// </summary> public class ChildClass1 : ParentClass { public override void ParVirMethod() { Console.WriteLine("子类1的方法..."); } } /// <summary> /// 子类2 /// </summary> public class ChildClass2 : ParentClass { public new void ParVirMethod() { Console.WriteLine("子类2的方法..."); } public void Test() { Console.WriteLine("子类2的其他方法..."); } }执行调用:
深究其原因,为何两者不同,是因为原理不同: override是重写,即将基类的方法在派生类里直接抹去重新写,故而调用的方法就是子类方法;而new只是将基类的方法在派生类里隐藏起来,故而调用的仍旧是基类方法。
应用举例
有这样的需要的,比如 People 类有一个 Run 方法,Man 和 Woman 这两个类都是继承自 People 的类,并且都重写(override)了 Run 这个方法(男人女人跑起步来不一样)。
现在有一群人一起跑步,有男人有女人。
我们可以把这些都装进一个People数组(假设为peoples)。
然后:
foreach(People p in peoples) // peoples中对象不同(即有男有女),用于实例化的子类就不同。 { p.Run(); // 故而,调用的方法也不同,实现了多态 }由于多态性,在调用 p.Run() 的时候 p 对象本身如果是男人就会自动调用男人的 Run 方法,是女人就会调用女人的 Run 方法。
依赖倒置原则
依赖倒置原则,DIP,Dependency Inverse Principle DIP的表述是:
1、高层模块不应该依赖于低层模块, 二者都应该依赖于抽象。
2、抽象不应该依赖于细节,细节应该依赖于抽象。
这里说的“依赖”是使用的意思,如果你调用了一个类的一个方法,就是依赖这个类,如果你直接调用这个类的方法,就是依赖细节,细节就是具体的类,但如果你调用的是它父类或者接口的方法,就是依赖抽象, 所以 DIP 说白了就是不要直接使用具体的子类,而是用它的父类的引用去调用子类的方法,这样就是依赖于抽象,不依赖具体。
其实简单的说,DIP 的好处就是解除耦合,用了 DIP 之后,调用者就不知道被调用的代码是什么,因为调用者拿到的是父类的引用,它不知道具体指向哪个子类的实例,更不知道要调用的方法具体是什么,所以,被调用代码被偷偷换成另一个子类之后,调用者不需要做任何修改, 这就是解耦了。
Jimmy
jia***gpay@outlook.com
参考地址
zzzain46
103***5168@qq.com
关于基类访问(访问隐藏的基类成员)
如果想要使得派生类能够完全访问被隐藏的继承成员,就可以使用基类访问表达式访问被隐藏的继承成员。
基类访问表达式由关键字base后面跟一个点和成员的名称组成。例如:
Console.WriteLine("{0}",base.Field1);上面一行代码中的 base.Fields1 就属于基类访问。
基类访问的实例:
class SomeClass //基类 { public string Field1 = "Fields -- In the base class"; } class OtherClass : SomeClass //继承类OtherClass,继承于SomeClass { new public string Field1 = "Fields -- In the derived class"; public void PrintField1() { Console.WriteLine(Field1); //访问派生类 Console.WriteLine(base.Field1) //访问基类 } } class Program { static void Main() { OtherClass oc = new OtherClass(); //实例化 oc.PrintFields1(); //执行oc的PrintFields1()方法 } }以上代码就是访问基类隐藏成员的实例,运行的结果是输出:
zzzain46
103***5168@qq.com
hiq
451***930@qq.com
创建子类对象调用子类的构造函数时,会首先调用父类的无参构造函数。
using System; namespace ConsoleApp1 { class Program { class father { public father() { Console.WriteLine("father"); } } class son : father { public son() { Console.WriteLine("son"); } } static void Main(string[] args) { son s = new son(); Console.ReadKey(); } } }输出结果:
hiq
451***930@qq.com
胡桃
266***8012@qq.com
关于继承的几点:
前面的几点都说得差不多了,说说第 5 点:
class Hardware //类Hardware是父类 { public string name; public double price; } class CPU : Hardware //类CPU是子类 { public string kind; } class MainBoard : CPU //第4点 类CPU是MainBoard的父类 { } class Display { } class Program { static void Main(string[] args) { CPU c = new CPU(); c.name = ""; c.price = 1; c.ToString(); //可以发现无论是父类还是子类中都没有ToString这个方法的 } } 在程序的任何地方输入 Object 后,鼠标放上去后右键,找到转到定义后点击或者直接按 F12,就可以看到以下代码: public class Object { // // 摘要: // 初始化 System.Object 类的新实例。 [NonVersionableAttribute] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] public Object(); // // 摘要: // 在垃圾回收将某一对象回收前允许该对象尝试释放资源并执行其他清理操作。 [NonVersionableAttribute] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] ~Object(); // // 摘要: // 确定指定的对象实例是否被视为相等。 // // 参数: // objA: // 要比较的第一个对象。 // // objB: // 要比较的第二个对象。 // // 返回结果: // 如果对象被视为相等,则为 true,否则为 false。 如果 objA 和 objB 均为 null,此方法将返回 true。 public static bool Equals(Object objA, Object objB); // // 摘要: // 确定指定的 System.Object 实例是否是相同的实例。 // // 参数: // objA: // 要比较的第一个对象。 // // objB: // 要比较的第二个对象。 // // 返回结果: // true如果objA是相同的实例作为objB或如果两者均null; 否则为false。 [NonVersionableAttribute] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] public static bool ReferenceEquals(Object objA, Object objB); // // 摘要: // 确定指定的对象是否等于当前对象。 // // 参数: // obj: // 要与当前对象进行比较的对象。 // // 返回结果: // 如果指定的对象等于当前对象,则为 true,否则为 false。 public virtual bool Equals(Object obj); // // 摘要: // 作为默认哈希函数。 // // 返回结果: // 当前对象的哈希代码。 public virtual int GetHashCode(); // // 摘要: // 获取当前实例的 System.Type。 // // 返回结果: // 当前实例的准确运行时类型。 [SecuritySafeCritical] public Type GetType(); // // 摘要: // 返回表示当前对象的字符串。 // // 返回结果: // 表示当前对象的字符串。 public virtual string ToString(); // // 摘要: // 创建当前 System.Object 的浅表副本。 // // 返回结果: // 当前 System.Object 的浅表副本。 [SecuritySafeCritical] protected Object MemberwiseClone(); }本人也是在学习中,如果有什么错误欢迎指正。
胡桃
266***8012@qq.com