【一起来阅读《C#图解教程》吧】-- 类和继承

类继承

通过继承我们可以定义一个新类,已存在的类称为基类(base class),新类称为派生类(derived class),派生类包含:

  • 本身声明中的成员
  • 基类的成员
class OtherClass : SomeClass
{
    //....
}

以上展示了一个继承SomeClass类的子类OtherClass。
继承的成员可以被访问,就像它们是派生类自己声明的一样。

除了特殊的类object,所有的类都是派生类,即使它们没有基类规格说明。类object是唯一的非派生类,因为它是继承层次结构的基础。没有基类规格说明的类隐式地直接派生自类object。

C#是单继承的,一个类只能继承一个基类。

屏蔽基类的成员

派生类不能删除它继承的任何成员,但可以用与基类成员名称相同的成员来屏蔽基类成员。这是继承的主要功能之一,非常实用。

  • 【字段】要屏蔽一个继承的数据成员,需要声明一个新的相同类型的成员,并使用相同的名称。
  • 【方法】通过在派生类中声明新的带有相同签名的函数成员,可以隐藏或屏蔽继承的函数成员。请记住,签名由名称和参数列表组成,不包括返回类型。
  • 可以屏蔽静态成员
    class Program
    {
        static void Main(string[] args)
        {
            OtherClass s = new OtherClass();
            s.Log();
            while (true)
            {
                
            }
        }
    }


    class SomeClass
    {
        public int A = 1;

        public void Log()
        {
            Console.WriteLine(A);
        }
    }

    class OtherClass:SomeClass
    {
        new public int A = 2;

        new public bool Log()
        {
            Console.WriteLine(A);
            return true;
        }
    }

以上代码输出为:2
注意,方法屏蔽时,方法签名相同时即可,签名不包括返回值。

使用基类的引用

派生类的实例由基类的实例加上派生类新增的成员组成。派生类的引用指向整个类对象,包括基类部分 。这句话的意思是说,我们可以用派生类的对象访问到基类中的成员。

如果有一个派生类对象的引用,就可以获取该对象基类部分的引用(使用类型转换运算符把该引用转换为基类类型)
image.png
上图,如果基类中的一个方法为打印字符串“xxx”,而子类屏蔽了该方法,并打印字符串“yyy”,那么最终用转换过的引用(上图的mybc)调用该方法,则是访问的是基类中的方法,应该打印“xxx”。

虚方法和覆写方法(virtual & override)

当我们使用屏蔽方法的时候,使用基类引用访问子类对象时,得到的是基类的成员,虚方法可以使基类的引用访问“升至”派生类内。不过需要满足以下:

  • 派生类的方法和基类的方法有相同的签名和返回类型
  • 基类的方法使用virtual标注
  • 派生类的方法使用override标注

image.png

  • 覆写和被覆写的方法必须有相同的可访问性。
  • 不能覆写static方法或非虚方法。
  • 方法、属性和索引器,以及另一种成员类型事件,都可以被声明为virtual和override。

override

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

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

构造函数的执行

在这里,你会看到派生类对象有一部分就是基类对象。

  • 要创建对象的基类部分,需要隐式调用基类的某个构造函数作为创建实例过程的一部分。
  • 继承层次链中的每个类在执行它自己的构造函数体之前执行它的基类构造函数。
    class MyDerivedClass: SomeClass
    {
        MyDerivedClass() //构造函数调用基类构造函数MyBaseClass()
        {

        }
    }

以上,创建一个实例过程中完成的第一件事是初始化对象的所有实例成员。在此之后,调用基类的构造函数,然后才执行该类自己的构造函数体 。
image.png

    class MyBaseClass
    {
        public MyBaseClass() //第二步:基类构造函数调用
        {
            //............
        }
    }

    class MyDerivedClass: MyBaseClass
    {
        private int a = 5;//第一步:成员初始化
        private int b;

        public MyDerivedClass() //第三步:构造函数体执行
        {

        }
    }

警告:在构造函数中调用虚方法是极不推荐的。在执行基类的构造函数时,基类的虚方法会调用派生类的覆写方法,但这是在执行派生类的构造函数方法体之前。因此,调用会在派生类没有完全初始化之前传递到派生类。

构造函数初始化语句

默认情况下,在构造对象时,将调用基类的无参数构造函数。但构造函数可以重载,所以基类可能有一个以上的构造函数。如果希望派生类使用一个指定的基类构造函数而不是无参数构造函数,必须在构造函数初始化语句中指定它。
有两种形式的构造函数初始化语句。

  • 第一种形式使用关键字base并指明使用哪一个基类构造函数。
  • 第二种形式使用关键字this并指明应该使用当前类的哪一个构造函数 。

基类构造函数初始化语句放在冒号后面,冒号紧跟着类的构造函数声明的参数列表。构造函数初始化语句由关键字base和要调用的基类构造函数的参数列表组成。

  • 构造函数初始化语句指明要使用有两个参数的基类构造函数。并且第一个参数是一个 string,第二个参数是一个int。
  • 在基类参数列表中的参数必须在类型和顺序方面与已定的基类构造函数的参数列表相匹配。
        public MyDerivedClass(int x,string s):base(s,x)

当声明一个不带构造函数初始化语句的构造函数时,它实际上是带有base()构造函数初始化语句的简写形式

        //以下两者等价
        public MyDerivedClass() 
        {

        }

        public MyDerivedClass() : base()
        {

        }

另外一种形式的构造函数初始化语句可以让构造过程(实际上是编译器)使用当前类中其他的构造函数。

        public MyClass(int x):this(x,"Default String")

这种语法很有用的另一种情况是,一个类有好几个构造函数,并且它们都需要在对象构造的过程开始时执行一些公共的代码。对于这种情况,可以把公共代码提取出来作为一个构造函数,被其他所有的构造函数作为构造函数初始化语句使用由于减少了重复的代码,实际上这也是推荐的做法

如果声明一个非构造方法来进行公共的初始化,然后让构造函数来调用这个方法,这通常不是很好的做法,因为首先构造函数本身可以进行一些优化,其次如readonly字段只能在构造函数中进行初始化。

类访问修饰符

  • public:可以在程序集之间被访问
  • internal:只能在自己的程序集被其他的类访问,别的程序集不可访问。当class前没有写任何标识时,他被认为是internal的,这也是默认的访问级别。

程序集间的继承

  • 如果基类和子类来自不同的程序集,那么基类应该被声明为public
  • 子类的程序集应该包含对基类的程序集的引用,即添加Reference

成员访问修饰符

public

访问级别限制最少,所有的类都可以访问,包括程序集内部和外部的类都可以访问

private

访问限制最严格,只能在自己的类中被访问,继承它子类中也不可访问。

protected

只要是继承该类的子类都可以访问,不管它在程序集内部还是外部

internal

对该类所在的程序集中其他类可见,而程序集的外部类不可见。

protected internal

字面意思,是protected 和 internal 范围的并集,内部或者是继承该类的都可以访问

抽象成员

抽象成员是指设计为被覆写的函数成员

  • 必须是一个函数成员。也就是说,字段和常量不能为抽象成员。
  • 必须用abstract修饰符标记
  • 不能有实现代码块。抽象成员的代码用分号表示。
  • 方法、属性、事件、索引可以声明为抽象的。

尽管抽象成员必须在派生类中用相应的成员覆写,但不能把virtual修饰符附加到 abstract修饰符。

抽象类

  • 抽象类就是指设计为被继承的类。抽象类只能被用作其他类的基类,不能创建抽象类的实例。
  • 抽象类使用abstract修饰符声明
  • 抽象类自己可以派生自另一个抽象类,任何派生自抽象类的类必须使用。override关键字实现该类所有的抽象成员,除非派生类自己也是抽象类。

密封类

与抽象类相反,密封类只能被用作独立的类,他不能被作为基类。密封类用sealed修饰符标注

sealed class MyClass
{
    ...
}

静态类

态类中所有成员都是静态的。静态类用于存放不受实例数据影响的数据和函数。静态类的一个常见的用途可能就是创建一个包含一组数学方法和值的数学库。

  • 类中的所有成员必须是静态的
  • 类可以有一个静态构造函数,不能有实例构造函数,不能创建该类的实例。
  • 使用类名和静态成员名来范文静态类的成员。

扩展方法

扩展方法扩展了“每个方法都和声明它的类关联” 这个边界,允许编写的方法和声明它的类之外的类关联。

如果有一个类,你想给它增加一个方法,但这个类可能是密封的或者在第三方库中,因此你不得不想一些额外的方法来对他进行扩展,你可以定义一些静态类和静态方法,将原本需要扩展的类的实例作为参数传进去,以此可以完成方法的扩展。但是它不够优雅,而扩展类就很好的解决了我们的问题。下面为具体实例

  • 声明扩展方法的类必须声明为static。
  • 扩展方法本身必须声明为static。
  • 扩展方法必须包含关键字this作为它的第一个参数类型,并在后面跟着它所扩展的类的名称。

posted @   LemonInCup  阅读(120)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示