C#学习笔记 -- 类继承、屏蔽与覆写基类方法

发布时间 2023-05-22 01:17:05作者: Phonk

1、类继承

通过继承可以定义一个新类, 新类纳入一个已经声明的类并进行扩展

  • 可以使用一个已经存在的类作为新类的基础, 已存在的类称为基类(base class), 新类为派生类(derived class), 派生类成员的组成如下

    • 本身声明中的成员

    • 基类的成员

  • 要声明一个派生类, 需要在类名后写基类规格说明

    • 基类规格说明由冒号和用作基类的类名名称组成

    • 派生类直接继承自列出的基类

  • 派生类扩展他的基类, 因为他包含了基类的成员, 还有他本身声明中的新增功能

  • 派生类不能删除他所继承的任何成员

class DerivedClass : BaseClass
{
    ...
}
class 派生类 : 基类
{
    
}

2、访问继承的成员

继承的成员可以被访问, 就像他们是派生类自己声明的一样(继承的构造函数有些不同)

class Program802Base
{
    public string FieldBase = "基类字段";
​
    public void MethodBase(string value)
    {
        Console.WriteLine($"基类方法, 输出{ value }");
    }
}
class Program802Derived : Program802Base
{
    public string FieldDerived = "派生类字段";
​
    public void MethodDerived(string value)
    {
        Console.WriteLine($"派生类方法, 输出{ value }");
    }
}
static void Main(string[] args)
{
    Program802Derived derived = new Program802Derived();
    derived.MethodBase(derived.FieldBase);
    derived.MethodBase(derived.FieldDerived);
    derived.MethodDerived(derived.FieldBase);
    derived.MethodDerived(derived.FieldDerived);
}

3、所有类都派生自object类

  • 除了object类, 别的类都是派生类, 因为object类是继承层次结构的基础

  • 没有基类规格说明隐式派生于object

注意
  1. 一个类声明的基类规格说明只能有一个单独的类, 这称为单继承

  2. 虽然只能直接继承一个类, 但派生的层次没有限制, 作为基类的类可以派生自另一个类, 另一个类可能派生于别的类, 直至最终达到object

4、屏蔽基类的成员

虽然派生类不能删除他继承的任何成员, 可以使用于基类名称相同的成员来屏蔽基类成员

  • 使用new关键字, 并且以同样的名称屏蔽基类成员

class BaseClass
{
    public string Field;
}
class DerivedClass
{
    new public string Field; //使用new关键字, 并且以同样的名称屏蔽基类成员
}
注意
  1. 要屏蔽一个继承的数据成员, 需要声明一个新的相同类型的成员, 并使用相同的名称

  2. 通过在派生类中声明新的带有相同签名的函数成员, 可以屏蔽继承的函数成员, 签名不包括返回类型

  3. 让编译器知道你在故意的屏蔽继承的成员, 可以使用new修饰符, 否则程序编译成功, 编译器警告你隐藏类一个继承的类

  4. 也可以屏蔽静态成员

例子
class Program804Base
{
    public string Field = "基类字段";
​
    public void Method(string value)
    {
        Console.WriteLine($"基类方法, 输出{ value }");
    }
}
class Program804Derived : Program804Base
{
    //使用new关键字屏蔽基类字段
    new public string Field = "派生类字段, 屏蔽基类同签名方法";
​
    //使用new关键字屏蔽基类方法
    new public void Method(string value)
    {
        Console.WriteLine($"派生类方法, 屏蔽基类同签名方法, 输出{ value }");
    }
}
static void Main(string[] args)
{
    Program804Derived program804Derived = new Program804Derived();
    program804Derived.Method(program804Derived.Field);
}

5、基类访问

如果派生类必须访问被隐藏的继承成员, 在派生类中, 可以使用基类访问表达式

base.Method();
base.Field;
例子
class Program805Base
{
    public string Field = "基类字段";
​
    public void Method(string value)
    {
        Console.WriteLine($"基类方法, 输出{ value }");
    }
}
class Program805Derived : Program804Base
{
    //使用new关键字屏蔽基类字段
    new public string Field = "派生类字段, 屏蔽基类同签名方法";
​
    //使用new关键字屏蔽基类方法
    new public void Method(string value)
    {
        Console.WriteLine($"派生类方法, 屏蔽基类同签名方法, 输出{ value }");
    }
    //基类访问
    public void UsingBaseExample()
    {
        base.Method(base.Field);
    }
}
static void Main(string[] args)
{
    Program805Derived program805Derived = new Program805Derived();
    program805Derived.UsingBaseExample();
}

6、基类的引用

派生类的实例由基类的实例和派生类新增成员组成, 派生类的引用指向整个对象, 包括基类部分

  • 如果有一个派生类对象的引用, 就可以获取该对象基类部分的引用

    MyDerivedClass derived = new MyDerivedClass();
    MyBaseClass mybc = (MyBaseClass) derived;//使用类型转换运算符, 把该引用转换为基类类型将派生类
  • 对象强制转换为基类对象的作用是产生的变量只能访问基类的成员(在被覆盖的方法中除外)

例子
class Program806Base
{
    public void Print()
    {
        Console.WriteLine("基类方法");
    }
}
class Program806Derived : Program806Base
{
    public int var1;
    new public void Print()
    {
        Console.WriteLine("派生类方法");
    }
}
static void Main(string[] args)
{
    Program806Derived derived = new Program806Derived();
    Program806Base mybc = (Program806Base) derived;
    mybc.Print();//基类方法
}

(1)虚方法和覆写方法

虚方法可以将基类的引用访问"升至"派生类中

可以使用基类调用派生类的方法, 只需满足下列条件

  • 派生类的方法和基类的方法有相同的签名和返回类型

  • 基类的方法使用virtual标注

  • 派生类的方法使用override标注

class MyBaseClass
{
    virtual public void Print()
}
class MyDerivedClass
{
    override public void Print()
}
注意: 与使用new关键字修饰的区别
  • 使用基类引用调用方法时候, 方法调用被传递到派生类并执行, 原因如下

    • 基类的方法被标记为virtual

    • 在派生类中有匹配的override方法

例子
class Base806_1
{
    virtual public void Print()
    {
        Console.WriteLine("基类方法");
    }
}
class Derived806_1 : Base806_1
{
    public override void Print()
    {
        Console.WriteLine("派生类方法");
    }
}
static void Main(string[] args)
{
    Derived806_1 derived = new Derived806_1();
    Base806_1 mybc = (Base806_1)derived;
    derived.Print(); //派生类方法
    mybc.Print(); //派生类方法
}
注意
  1. 覆写和被覆写的方法必须有相同的可访问性

  2. 不能覆写静态方法或者非虚方法

  3. 方法、属性、索引器、事件都可以被声明为virtual和override

(2)覆写标记为override的方法

覆写方法可以在继承的任何层次出现

  • 当使用对象基类部分的引用调用一个被覆写的方法时, 方法的调用被沿派生层次上追溯执行, 一直到标记为override的方法的最高派生(most-derived)版本

  • 如果在更高的派生级别有该方法的其他声明, 但没有被标记为override, 那么他们不会被调用

探讨例子
//基类
class MybaseClass
{
    virtual public void Print()
    {
        Console.WriteLine("基类方法");
    }
}
//派生类
class MyDerivedClass : MybaseClass
{
    override public void Print()
    {
        Console.WriteLine("派生类方法");
    }
}
1)使用override声明顶级派生类的Print方法
class SecondDerived : MyDerivedClass
{
    override public void Print()
    {
        Console.WriteLine("顶级派生类方法");
    }
}

覆写方法的两个低派生级别版本, 如果一个基类的引用被用于调用Print, 就会一直向上传递直到顶级派生类SecondDerived

static void Main(string[] args)
{
    SecondDerived derived = new SecondDerived();
    MyBaseClass mybc = (MyBaseClass) derived;
​
    derived.Print(); //顶级派生方法
    mybc.Print(); //顶级派生方法
}

结论

  • 无论是通过派生类还是基类调用, 都会调用最高派生类中的方法,

  • 通过基类调用时, 调用沿着继承层次向上传递.

2)使用new声明顶级派生类的Print方法
class SecondDerived : MyDerivedClass
{
    //使用new声明顶级派生类的Print方法
    new public void Print()
    {
        Console.WriteLine("顶级派生类方法");
    }
}

通过mybc调用Print方法时, 方法调用只向上传递了一级, 到达派生类, 在那里被执行, 隐藏覆写的方法

static void Main(string[] args)
{
    SecondDerived derived = new SecondDerived();
    MyBaseClass mybc = (MyBaseClass)derived;
​
    derived.Print(); //顶级派生方法
    mybc.Print(); //派生类方法
}

(3)覆盖其他成员

virtual/override可以覆盖方法、属性、事件、索引器

例子: 覆盖只读属性
class Base806_3
{
    public int _myProperty = 5;
    virtual public int MyProperty
    {
        get { return _myProperty; }
    }
}
class Derived806_3 : Base806_3
{
    //后备字段如果不屏蔽, 编译器会报错, 执行是没有问题的
    new private int _myProperty = 10;
    override public int MyProperty
    {
        get { return _myProperty; }
    }
}
static void Main(string[] args)
{
    Derived806_3 derived = new Derived806_3();
    Base806_3 mybc = derived;
    Console.WriteLine(derived.MyProperty);//10
    Console.WriteLine(mybc.MyProperty);//10
}