【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来强制转换

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

相关文章
|
2月前
|
C#
C#学习相关系列之数据类型类的三大特性(二)
C#学习相关系列之数据类型类的三大特性(二)
|
2月前
|
C#
58.c#:directory类
58.c#:directory类
13 0
|
2月前
|
C#
57.c#:directorylnfo类
57.c#:directorylnfo类
13 0
|
2月前
|
监控 C#
55.c#:file类
55.c#:file类
17 1
|
2月前
|
算法 C#
54.c#:random类
54.c#:random类
15 1
|
2月前
|
C#
51.c#:string类的静态方法
51.c#:string类的静态方法
21 1
|
2月前
|
C#
27.c#关键字sealed修饰类
27.c#关键字sealed修饰类
12 0
|
4月前
|
Java C#
C# 面向对象编程解析:优势、类和对象、类成员详解
OOP代表面向对象编程。 过程式编程涉及编写执行数据操作的过程或方法,而面向对象编程涉及创建包含数据和方法的对象。 面向对象编程相对于过程式编程具有几个优势: OOP执行速度更快,更容易执行 OOP为程序提供了清晰的结构 OOP有助于保持C#代码DRY("不要重复自己"),并使代码更易于维护、修改和调试 OOP使得能够创建完全可重用的应用程序,编写更少的代码并减少开发时间 提示:"不要重复自己"(DRY)原则是有关减少代码重复的原则。应该提取出应用程序中常见的代码,并将其放置在单一位置并重复使用,而不是重复编写。
51 0
|
2月前
|
C#
深入C#中的String类
深入C#中的String类
11 0
|
2月前
|
C#
C#学习系列相关之多线程(二)----Thread类介绍
C#学习系列相关之多线程(二)----Thread类介绍