WarrenRyan

.NET Core CSharp初级篇 1-5

本节内容类的接口、枚举、抽象

简介

问题

  • 如果你需要表示星期或者是某些状态,使用字符串或者数字是否不直观?
  • 你是否发现,无论何种电脑,它的USB口的设计都是遵循一定规范的?

    枚举

    枚举(enum)是一个非常好用的一个特殊值类型,他可以让你指定一系列字符常量(通常从0开始)。它的定义和使用如下:
public enum Week
{
    Monday,
    ...//此处省略
    Sunday = 6//可赋值
}
bool flag = (6 == (int)Week.Sunday)

不过你也可以指定其他的类型作为枚举的值,例如:

public enum Week:byte
{
    Monday,
    ...
    Sunday= 6
}

枚举与其他类型的转换使用强制类型转换即可,例如:(int)Week.Sunday,不过特别的,0不需要强制转换就可以和枚举进行比较。

Flag Enum

这是一个有趣的枚举,它支持你对枚举按位进行运算,使用flag enum需要在枚举名上面指定一个Attribute,也就是[Flags],通常来说,我们会使用2的幂作为枚举值,因为按位运算本质是2进制的运算。

具体实例如下

[Flags]
public enum Status
{
    Success = 1,
    NotFound = 2,
    Fail = 4
}
//支持按位运算,运算步骤我们在之前已经有过详细的讲解
Status.Success | Status.NotFound

默认的,如果你输入了一个不合理的枚举值(也就是没定义),编译器会默认直接输出该数字,不过如果你使用了按位运算的枚举,那么他会将你输入的数字转换成二进制与每一个枚举值进行&运算,得出的结果会与枚举值进行比较,如果找到了就会输出。

例如以上例的Status:

Console.WriteLine((Status)7);
//输出是三个都输出
//7 = 0111,
//1 = 0001,
//2 = 0010
//4 = 0011

接口

接口这个东西,新手非常容易被误导,例如在WebApi开发中,你的前端朋友让你把接口给他,这个时候,他需要的东西在后端的口中叫做API,
当你的后端朋友说,你写一个接口,我们使用依赖注入进行统一管理实现了接口的类,这个时候,他需要的是一个约定,也是我们这里讲的接口(interface)。

接口是C#面向对象中实现多态的重要语法。接口的定义可以理解为是一种约定的规范。例如电脑的USB-A接口,全世界的厂商都是统一规范生产,如果大家不按着约定生产,后果会是什么?

在C#中,接口也一样起到了这个作用,但是还有一些更为广泛的应用。

接口的定义使用interface关键字,默认的,所有接口的成员的访问权限都是public,因为规范是需要公布给所有人看,如果定义了访问权限就没有实际的意义了
,并且接口中所有的函数都不存在函数体。总的来说就是,接口是一个提供类的规格的东西,却不提供实现。

接口的例子

//定义一个接口
public inteface IHuman
{
    void Eat();
    bool Alive();
}
//接口的实现,必须实现每一个接口中的函数并保持返回类型、函数签名,函数参数一致
public class Human:IHuman
{
    void Eat()
    {

    }
    bool Alive()
    {
        return default<bool>();
    }
}

接口的实现会和我们后面讲到的继承非常相似,在这里,你只需要记住接口支持一个类实现多个接口,但只能继承一个类即可。

接口冲突

因为支持一个类实现多个接口,那么很有可能会造成A接口中拥有和B接口中完全一致的函数,这个时候我们就需要使用显式实现接口进行处理。

当你需要调用不同接口的同签名方法时,使用接口进行强制转换即可。

例如:

interface IApple
{
    void Wash();
}
interface IFruit
{
    void Wash();
}
class test : IApple,IFruit
{
    void IApple.Wash()=>{};
    void IFruit.Wash()=>{};
}
test t= new test();
((IApple)t).Wash();
((IFruit)t).Wash();

这样就可以避免接口在命名上的冲突。

并且接口如果你隐式的实现,所有接口函数默认都是sealed的。
如果存在一个多继承的问题,这个可能目前讲起来为之过早,我就顺口提一下,例如,人类继承与动物类,动物继承于生物接口,
那么对于人类,是隐式的继承了生物接口,但是对于人类和动物,进食的方式有很大区别,那么我们就应该重写进食这个方法。
我们就要把基类(父类)中的接口函数标记为virtual或者abstract,然后在子类中使用override进行重写。

这就已经说的太远了,后面我们会进行深入的刨析。

抽象

抽象可以有抽象字段、抽象类、抽象委托、抽象函数等等。我们就以其中常用的抽象函数和抽象类做一个解析。

抽象和接口非常相似,抽象类不能被实例化,抽象方法没有方法体,都是依赖子类(被继承类)进行操作。

抽象函数

这个就和接口几乎一模一样,也没有太多讲的必要,如果你声明了一个函数是抽象函数,那么它不存在方法体,你需要通过子类去重写(override)实现。

在实际应用中,子类仅能重写父类中的虚方法或者抽象方法,当不需要使用父类中方法的内容时,将其定义成抽象方法,否则将方法定义成虚方法。

抽象类

"一个包含一个或多个纯虚函数的类叫抽象类,抽象类不能被实例化,进一步
一个抽象类只能通过接口和作为其它类的基类使用."

一个抽象类可以包含抽象和非抽象方法,当一个类继承于抽象类,那么这个派生类必须实现所有的
的基类抽象方法。

但是通过声明派生类也为抽象,我们可以避免所有或特定的虚方法的实现,
这就是抽象类的部分实现。

看起来很高深?事实上抽象类就是一个提供了有部分没有方法体的函数和有具体实现的函数的集合。它相比于接口毫无实现而言,抽象类可以提供非抽象的方法,也就是说,抽象类中可以含有有实现方法的函数。

看这个例子

public abstract class A
{
    public void GetSomeThing()
    {
        //todo
    }
    public abstract void SetSomeThing();
}
public class B:A
{
    //实现抽象方法
    public override void SetSomeThing()
    {
        //调用非抽象方法
        base.GetSomeThing();
    }

}

这里面涉及到了base关键字以及":"继承符号,在后面的继承、多态的课程有会有更加深入的介绍。

Github

BiliBili主页

WarrenRyan's Blog

博客园

相关文章: