关于C#继承与多态的思考

本文详细介绍了 C# 中的继承规则,包括传递性、构造函数、基类成员访问、密封类及抽象类等内容,并深入探讨了多态性,包括编译时和运行时的多态性,以及如何通过虚方法实现多态。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在网上看到了一句话:继承是子类使用父类的方法,而多态则是父类使用子类的方法。

我觉得这句话说的还是蛮不错的

继承

C#中的继承符合下列规则:

1、继承是可传递的。如果C从B中派生,B又从A中派生,那么C不仅继承了B中声明的成员,同样也继承了A中的成员。Object 类作为所有类的基类。

2、派生类(子类)应当是对基类(父类)的扩展。派生类可以添加新的成员,但不能除去已经继承的成员的定义。

3、构造函数和析构函数不能被继承。除此以外的其它成员,不论对它们定义了怎样的访问方式,都能被继承。基类中成员的访问方式只能决定派生类能否访问它们。

4、派生类如果定义了与继承而来的成员同名的新成员,就可以覆盖已继承的成员。但这并不因为这派生类删除了这些成员,只是不能再访问这些成员。

5、类可以定义虚方法、虚属性以及虚索引指示器,它的派生类能够重载这些成员,从而实现类可以展示出多态性。

6、派生类只能从一个类中继承,可以通过接口实现多重继承。

   using System;
   
   public class ParentClass
   {
      public ParentClass( )
      {
          Console.WriteLine("我是父类构造函数"); 
      }
      public void print( )
      {
          Console.WriteLine("I'm a Parent Class") ;
       }
   }

   public class ChildClass : ParentClass
   {
       public ChildClass( )
       {
            Console.WriteLine("我是子类构造函数") ;
       }
       public static void Main( )
       {
           ChildClass child = new ChildClass( ) ;
           child.print( ) ;
       }
    } 
这段代码运行后的输出就会是这样的:

父类构造函数
子类构造函数
I'm a Parent Class

正如前面所说的,子类会继承父类除了构造函数和析构函数以外的所有属性(方法以及变量)

然后就是关于通过 base 关键字访问基类的成员:

base关键字的作用如下:

1. 调用基类上已被其他方法重写的方法。

2. 指定创建派生类实例时应调用的基类构造函数。

3. 基类访问只能在构造函数、实例方法或实例属性访问器中进行。

最后注意一点:

从静态方法中使用 base 关键字是错误的!!

实例代码如下:

using System ;

public class Parent
{
   string parentString;
   public Parent( )
   {
       Console.WriteLine("Parent Constructor.") ;
   }
   public Parent(string myString)
   {
       parentString = myString;
       Console.WriteLine(parentString) ;
   }
   public void print( )
   {
       Console.WriteLine("I'm a Parent Class.") ;
   }
}

public class Child : Parent
{
   public Child( ) : base("From Derived")
   {
      Console.WriteLine("Child Constructor.") ;
   }
   public void print( )
   {
      base.print( ) ;
      Console.WriteLine("I'm a Child Class.") ;
   }
   public static void Main( )
   {
      Child child = new Child( ) ;
      child.print( ) ;
      ((Parent)child).print( ) ;
   }
} 
程序运行输出:

From Derived
Child Constructor.
I'm a Parent Class.
I'm a Child Class.
I'm a Parent Class.

其中:

((Parent)child).print( );

这段代码与在子类中定义的这段代码

base.print( ) ;

是一样的效果,只是前者可在类外进行强制转换输出,后者必须定义在子类的方法之中。

密封类

想想看,如果所有的类都可以被继承,继承的滥用会带来什么后果?类的层次结构体系将变得十分庞,大类之间的关系杂乱无章,对类的理解和使用都会变得十分困难。有时候,我们并不希望自己编写的类被继承。另一些时候,有的类已经没有再被继承的必要。C#提出了一个

密封类(sealed class)

的概念,帮助开发人员来解决这一问题。

密封类在声明中使用sealed 修饰符,这样就可以防止该类被其它类继承。如果试图将一个密封类作为其它类的基类,C#就会提示出错。

理所当然,密封类不能同时又是抽象类,因为抽象类总是希望被继承的。

密封类可以阻止其它程序员在无意中继承该类。而且密封类可以起到运行时优化的效果。实际上,密封类中不可能有派生类。如果密封类实例中存在虚成员函数,该成员函数可以转化为非虚的,函数修饰符 virtual 不再生效。

而抽象类也有关键词 :Abstract

为了明确声明不允许创建某个类的实例,必须将那个类显式地声明为抽象类,抽象类试用于一些通用类。

这些类可能是某些派生类的基类,但使用时又不必要实例化。

抽象类可以包含抽象方法,抽象方法类似于virtual方法,但它不包含方法主体,必须(重点) 在派生类中重写(override)方法。

实例代码如下:

 abstract class A
   {
       public abstract void F( ) ;
   }

   sealed class B: A
   {
       public override void F( )
       { // F 的具体实现代码 }
   }

如果我们尝试写下面的代码

class C: B{ }

C#会指出这个错误,告诉你B是一个密封类,不能试图从B中派生任何类。

而除了密封类,C#还提出了密封方法这一概念

然而不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed 修饰符总是和override 修饰符同时使用。(密封方法中的sealed关键词只能在重写这个方法时使用!表示这个重写过的函数不能再被重写。(不可以在基类中使用))

实例代码如下:

using System ;

class A
{
   public virtual void F( )
   {
       Console.WriteLine("A.F") ;
   }
   public virtual void G( )
   {
       Console.WriteLine("A.G") ;
   }
}

class B: A
{
    sealed override public void F( )
    {
        Console.WriteLine("B.F") ;
    }
    override public void G( )
    {
        Console.WriteLine("B.G") ;
    }
}

class C: B
{
   override public void G( )
   {
       Console.WriteLine("C.G") ;
   }
} 

类B对基类A中的两个虚方法均进行了重载,其中F 方法使用了sealed 修饰符,成为一个密封方法。G 方法不是密封方法,所以在B的派生类C中,可以重载方法G,但不能重载方法F。

PS:关于使用接口实现多级继承的办法我就直接贴代码了,比较简单,自行参悟

using System ;

//定义一个描述点的接口
interface IPoint
{
   int x
   {
       get ;
       set ;
   }
}

//定义第二个描述点的接口
interface IPoint2
{
   int y
   {
       get ;
       set ;
   }
}

//在point中继承了两个父类接口,并分别使用了两个父类接口的方法
class Point:IPoint,IPoint2
{
   //定义两个类内部访问的私有成员变量
   private int pX ;
   private int pY ;
   public Point(int x,int y)
   {
       pX=x ;
       pY=y ;
   }
   
   //定义的属性,IPoint接口方法实现
   public int x
   {
      get
      {
          return pX ;
      }
      set
      {
          pX =value ;
      }
   }
   
   //定义的属性,IPoint2接口方法实现
   public int y
   {
       get
       {
           return pY ;
       }
       set
       {
           pY =value ;
       }
   }
}

多态

在C#中,多态性的定义是:同一操作作用于不同的类的实例,不同的类将进行不同的解释,最后产生不同的执行结果。C#支持两种类型的多态性:

● 编译时的多态性

编译时的多态性是通过重载来实现的。对于非虚的成员来说,系统在编译时,根据传递的参数、返回的类型等信息决定实现何种操作。

● 运行时的多态性

运行时的多态性就是指直到系统运行时,才根据实际情况决定实现何种操作。C#中,运行时的多态性通过虚成员实现。

 编译时的多态性为我们提供了运行速度快的特点,而运行时的多态性则带来了高度灵活和抽象的特点。

多态性是类为方法(这些方法以相同的名称调用)提供不同实现方式的能力。多态性允许对类的某个方法进行调用而无需考虑该方法所提供的特定实现。例如,每个类当中都写有一个Draw函数,你就可以通过调用不同的类,去指定系统应该运行哪个类的Draw函数。从而为函数的重载打下了基础。

实例代码如下:

using System ;

public class DrawingBase

{
   public virtual void Draw( )
   {
     Console.WriteLine("I'm just a generic drawing object.") ;
   }
}

public class Line : DrawingBase
{
   public override void Draw( )
   {
       Console.WriteLine("I'm a Line.") ;
   }
}

public class Circle : DrawingBase
{
   public override void Draw( )
   {
      Console.WriteLine("I'm a Circle.") ;
   }
}

public class Square : DrawingBase
{
   public override void Draw( )
   {
     Console.WriteLine("I'm a Square.") ;
   }
}

public class DrawDemo
{
   public static int Main(string[] args)
   {
       DrawingBase [] dObj = new DrawingBase [4];
       dObj[0] = new Line( ) ;
       dObj[1] = new Circle( ) ;
       dObj[2] = new Square( ) ;
       dObj[3] = new DrawingBase( ) ;
       foreach (DrawingBase drawObj in dObj)
       		drawObj.Draw( ) ;
       return 0;
   }
}
输出结果是:

I'm a Line.
I'm a Circle.
I'm a Square.
I'm just a generic drawing object.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值