【C#本质论 七】类-从设计的角度去认知(继承)(下)

简介: 【C#本质论 七】类-从设计的角度去认知(继承)(下)

重写

重写的概念已经很熟了,就像上文列举的,为了扩展而准备,需要注意以下几点:

  • C#支持重写实例方法和属性、不支持字段和任何静态成员重写
  • C#重写的成员必须显示添加virtual关键字,C#默认方法非虚
  • 如果派生类要重写方法,必须显式的使用override关键字!
  • 重写后的override修饰的方法还是虚方法,还能被继承重写

还有一个重要的修饰符就是new,可以在派生类显式的隐藏从基类继承的成员! 说白了new的一个重要作用是,如果派生类使用了和基类相同的方法签名,不管这个方法是虚方法还是实例方法,都会隐藏基类的方法而用自己的实现。

脆弱的基类:new修饰符

还有一点值得注意:虽然运行时调用派生的最远的虚成员,但如果遇到new就会变为,new之前的派生的最远的虚成员

public class Program
    {
        public class BaseClass
        {
            public void DisplayName()
            {
                Console.WriteLine("BaseClass");
            }
        }
        public class DerivedClass : BaseClass
        {
            // Compiler WARNING: DisplayName() hides inherited 
            // member. Use the new keyword if hiding was intended
            public virtual void DisplayName()
            {
                Console.WriteLine("DerivedClass");
            }
        }
        public class SubDerivedClass : DerivedClass
        {
            public override void DisplayName()
            {
                Console.WriteLine("SubDerivedClass");
            }
        }
        public class SuperSubDerivedClass : SubDerivedClass
        {
            public new void DisplayName()
            {
                Console.WriteLine("SuperSubDerivedClass");
            }
        }
        public static void Main()
        {
            SuperSubDerivedClass superSubDerivedClass
                = new SuperSubDerivedClass();
            SubDerivedClass subDerivedClass = superSubDerivedClass;
            DerivedClass derivedClass = superSubDerivedClass;
            BaseClass baseClass = superSubDerivedClass;
            superSubDerivedClass.DisplayName();  //SuperSubDerivedClass
            subDerivedClass.DisplayName(); //SubDerivedClass
            derivedClass.DisplayName();//SubDerivedClass
            baseClass.DisplayName();//BaseClass
        }
    }

这里最远的是SuperSubDerivedClass ,因为派生链最终指向了 SuperSubDerivedClass superSubDerivedClass= new SuperSubDerivedClass()产生的实例对象,如果为:

using System;
public class Program
{
    public class BaseClass
    {
        public virtual void DisplayName()
        {
            Console.WriteLine("BaseClass");
        }
    }
    public class DerivedClass : BaseClass
    {
        // Compiler WARNING: DisplayName() hides inherited
        // member. Use the new keyword if hiding wasb intended
        public override void DisplayName()
        {
            Console.WriteLine("DerivedClass");
        }
    }
    public class SubDerivedClass : DerivedClass
    {
        public override void DisplayName()
        {
            Console.WriteLine("SubDerivedClass");
        }
    }
    public class SuperSubDerivedClass : SubDerivedClass
    {
        public new void DisplayName()
        {
            Console.WriteLine("SuperSubDerivedClass");
        }
    }
    public static void Main()
    {
        SuperSubDerivedClass superSubDerivedClass
            = new SuperSubDerivedClass();
        DerivedClass derivedClass1 = new DerivedClass();
        SubDerivedClass subDerivedClass = superSubDerivedClass;
        DerivedClass derivedClass = subDerivedClass;
        BaseClass baseClass = derivedClass1;
        superSubDerivedClass.DisplayName();  //SuperSubDerivedClass
        subDerivedClass.DisplayName(); //SubDerivedClass
        derivedClass.DisplayName();//SubDerivedClass
        baseClass.DisplayName();//DerivedClass
    }
}
    }

baseClass.DisplayName(), 这里的最远就是DerivedClass里new出来的实例。

密封方法,禁止重写

和密封类一样,也有密封方法,就是子类继承实现后,不允许孙类继续继承,sealed和override是紧密配合使用的!

namespace AddisonWesley.Michaelis.EssentialCSharp.Chapter07.Listing07_15
{
    class A
    {
        public virtual void Method()
        {
        }
    }
    class B : A
    {
        public override sealed void Method()
        {
        }
    }
    class C : B
    {
        // ERROR:  Cannot override sealed members
        //public override void Method()
        //{
        //}
    }
}

base修饰符

和this一样,base可以用于调用基类的成员,显式调用基类的实现,这个时候不用实现最远的派生,就是自己的方法实现(即使这个方法是虚方法)

有了base修饰符,就能在代用派生类构造函数前确定要调用哪一个基类的构造函数。

public class PdaItem
    {
        public PdaItem(string name)
        {
            Name = name;
        }
        // ...
        public string Name { get; set; }
    }
    public class Contact : PdaItem
    {
        public Contact(string name) :
            base(name)
        {
            Name = name;
        }
        public new string Name { get; set; }
        // ...
    }

抽象类

抽象类说白了,就是仅供派生的类,无法实例化,与之相反的是,不抽象可直接实例化的是具体类

public abstract class PdaItem
    {
        public PdaItem(string name)
        {
            Name = name;
        }
        public virtual string Name { get; set; }  //虚成员已被基类实现,可以派生类被重写实现
        public abstract string GetSummary();     //抽象方法未被基类实现,必须被派生类实现!
    }

虚成员已被基类实现,可以派生类被重写实现,抽象成员未被基类实现,必须被派生类实现!并且**抽象成员(未被实现的方法或属性)**自动为虚。

System.Object

在万类始祖提供的方法里,

模式匹配

这部分内容主要聊聊两个操作符:is和as

is操作符

is作用是检查一个对象是否兼容于其他指定的类型,并返回一个Bool值如果一个对象是某个类型或是其父类型的话就返回为true,否则的话就会返回为false,永远不会抛出异常,如果对象引用为null,那么is操作符总是返回为false,因为没有对象可以检查其类型。

public static void Save(object data)
        {
            //is操作符的一般用法是向下显式转型之前,使用is来确定实例对象是否兼容于对应类型?
            if (data is string)    //如果data为null也返回false
            {
                string text = (string)data;
                if (text.Length > 0)  
                {
                    data = Encrypt(text);
                    // ...
                }
            }         
        }

即使这个对象是类型的子类型,转型也会失败哦,易错点,记住了(虽然编译的时候不会报错):

从C#7.0开始,is增加了模式匹配:

public static void Save(object data)
        {
            if (data is string text && text.Length > 0)  
            {
                data = Encrypt(text);
                // ...
            }
            else if (data is null)
            {
                throw new ArgumentNullException(nameof(data));
            }
            // ...
            Console.WriteLine(data);
        }

data is string text,如果data是string类型,则返回true,同时把值赋给text,这样就不需要两步操作了。主要用途就是判断一个数据项是否属于特定类型

switch对模式匹配的支持

之前的博客里简单聊过从C#7.0开始,支持模式匹配,但没有详细介绍过:

static public void Eject(Storage storage)
        {
            switch (storage)
            {
                case null: // The location of case null doesn't matter
                    throw new ArgumentNullException(nameof(storage));
                // ** Causes compile error because case statments below
                // ** are unreachable
                // case Storage tempStorage:
                //    throw new Exception();
                //    break;
                case UsbKey usbKey when usbKey.IsPluggedIn:
                    usbKey.Unload();
                    Console.WriteLine("USB Drive Unloaded!");
                    break;
                case Dvd dvd when dvd.IsInserted:
                    dvd.Eject();
                    Console.WriteLine("DVD Ejected!");
                    break;
                case Dvd dvd when !dvd.IsInserted:
                    throw new ArgumentException("There was no DVD present.");
                case HardDrive hardDrive:
                    throw new InvalidOperationException();
                default:   // The location of case default doesn't matter
                    throw new ArgumentException(nameof(storage));
            }
        }

有以下几点需要注意:

  • 和基本switch语句不同,模式匹配case子句不限于有常量值的类型(string,int,long,enum等)。相反,任何类型都可使用
  • 模式匹配case标签在类型后声明一个变量:case HardDrive hardDrive:该变量的作用域限于当前switch小节(始于case标签,中间是一个或多个语句,结束于跳转语句。)
  • 模式匹配case标签支持条件表达式,允许对条件进行额外筛选,例如:case UsbKey usbKey when usbKey.IsPluggedIn
  • 模式匹配switch小节的顺序变得重要。为基类写一个case标签,且不添加任何条件表达式(例如只写case Storage storage:),后面为派生类写的switch块都不会执行而且不写条件表达式,后边如果有case语句,编译器会报错。但如果基类的case标签写了条件表达式(编译时解析不了),之后的派生类标签都会被屏蔽
  • 为null写的switch小节可在任意位置,它解析为true的条件总是具有唯一性。(和基本switch语句一样,default标签位置还是随意。)
  • 允许针对相同类型写多个模式匹配case标签,前提是其中最多一个没有条件表达式,而且要想有条件表达式的执行,这个没有的要放到最后
  • 常量switch小节可以和模式匹配switch小节混合使用。当然优先考虑简化
  • 模式匹配switch小节仍然需要跳转语句。
  • case子句不允许使用可空类型(例如int?)。改为使用非可空版本。这是因为空值会匹配case null,永远不会匹配针对可空类型的case子句

暂时不需要太深入的去了解,在实践中验证吧。

as操作符

比起is操作符,as操作符的不同是:直接进行转型,转型成功则直接使用该类型的引用,如果转型失败返回null

static object Print(IDocument document)
        {
            if(document != null)
            {
                // Print document...
            }
            else
            {
            }
            return null;
        }
        static void Main()
        {
            object data = new object();
            // ...
            Print(data as Document);
        }

as有个缺点就是不能判断基础类型,也就是不能用在值类型里:AS是引用类型类型的转换或者装箱转换,不能用与值类型的转换。如果是值类型只能结合is来强制转换

本篇博客详细介绍了继承这一特性,顺便梳理了下几个操作符的使用,对转型操作有了更深入的理解。

相关文章
|
26天前
|
开发框架 .NET C#
C#|.net core 基础 - 删除字符串最后一个字符的七大类N种实现方式
【10月更文挑战第9天】在 C#/.NET Core 中,有多种方法可以删除字符串的最后一个字符,包括使用 `Substring` 方法、`Remove` 方法、`ToCharArray` 与 `Array.Copy`、`StringBuilder`、正则表达式、循环遍历字符数组以及使用 LINQ 的 `SkipLast` 方法。
|
2月前
|
存储 C# 索引
C# 一分钟浅谈:数组与集合类的基本操作
【9月更文挑战第1天】本文详细介绍了C#中数组和集合类的基本操作,包括创建、访问、遍历及常见问题的解决方法。数组适用于固定长度的数据存储,而集合类如`List<T>`则提供了动态扩展的能力。文章通过示例代码展示了如何处理索引越界、数组长度不可变及集合容量不足等问题,并提供了解决方案。掌握这些基础知识可使程序更加高效和清晰。
73 2
|
25天前
|
编译器 C#
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
113 65
|
2月前
|
安全 C#
C# 面向对象编程的三大支柱:封装、继承与多态
【9月更文挑战第17天】在C#中,面向对象编程的三大支柱——封装、继承与多态,对于编写安全、可维护、可复用的代码至关重要。封装通过访问修饰符和属性保护数据;继承允许子类继承父类的属性和方法,实现代码复用和多态;多态则提高了代码的灵活性和通用性。掌握这三大概念能显著提升C#编程能力,优化开发效率和代码质量。
|
24天前
|
Java 程序员 C#
【类的应用】C#应用之派生类构造方法给基类构造方法传参赋值
【类的应用】C#应用之派生类构造方法给基类构造方法传参赋值
9 0
|
2月前
|
存储 C#
C# 一分钟浅谈:继承与多态性的实践
【9月更文挑战第2天】本文从基础入手,详细介绍了面向对象编程中继承与多态性的核心概念。通过 `Animal`、`Dog` 和 `Cat` 类的示例代码,展示了如何利用继承重用代码及多态性实现不同对象对同一方法的多样化响应,帮助读者更好地理解和应用这两个重要概念,提升面向对象编程能力。
39 3
|
2月前
|
C# 数据安全/隐私保护
C# 一分钟浅谈:类与对象的概念理解
【9月更文挑战第2天】本文从零开始详细介绍了C#中的类与对象概念。类作为一种自定义数据类型,定义了对象的属性和方法;对象则是类的实例,拥有独立的状态。通过具体代码示例,如定义 `Person` 类及其实例化过程,帮助读者更好地理解和应用这两个核心概念。此外,还总结了常见的问题及解决方法,为编写高质量的面向对象程序奠定基础。
24 2
|
3月前
|
C#
C#中的类和继承
C#中的类和继承
40 6
|
3月前
|
C# 索引
C# 面向对象编程(二)——继承
C# 面向对象编程(二)——继承
37 0
|
3月前
|
Java C# 索引
C# 面向对象编程(一)——类
C# 面向对象编程(一)——类
33 0