C#学习笔记 - C#基础知识 - C#从入门到放弃 - C# 接口、抽象与密封

更多C#基础知识点可查看:C#学习笔记 - C#基础知识 - C#从入门到放弃

第10节 接口、抽象与密封

10.1 接口的声明

在C#中,接口(interfaces)是一种定义了一组合同类型的成员(方法、属性、索引器和事件)的契约。接口定义了一种抽象的行为规范,而不关注具体的实现细节。类可以实现(implement)一个或多个接口,实现接口的类需要提供接口中定义的全部成员的具体实现。

接口的作用主要有以下几点:
规范行为:接口定义了一组行为规范,类通过实现接口来遵循规范。这有助于提高代码的一致性和可读性。
多态性:通过接口,可以通过接口类型的引用来引用实现了接口的类的对象。这使得我们可以编写更灵活的代码,并实现多态性。
支持多重继承:C#中不支持类的多重继承,但是一个类可以实现多个接口。这使得我们可以通过多个接口来组合不同的行为,并避免了单继承的限制。
接口隔离原则:接口提供了一种将抽象与实现分离的方式,符合面向对象设计的接口隔离原则。接口使得类与类之间的耦合度降低,提高了代码的可维护性和可扩展性。

1、接口声明
语法:

修饰符 interface 接口名称	//接口名称一般以I开头able结尾
{
   
成员;
}
    interface IEatable		//接口名称一般以I开头able结尾
    {
   
        //接口默认声明为public   类的的默认声明是private
        //接口中不能有访问修饰符
        //由于接口中不能有字段,所以属性经常被写作为自动属性
        //private string _name;
        //属性
        string Name
        {
   
            get;
            set;
        }
        //方法  方法不能包含方法体
        void food();
    }

2、【代码示例】

interface IResizable
{
   
    void Resize(int width, int height);
    double Scale {
    get; set; }
}

class Rectangle : IResizable
{
   
    public void Resize(int width, int height)
    {
   
        // 实现具体的尺寸调整逻辑
    }
    public double Scale
    {
   
        get {
    return 1.0; }
        set {
    /* 实现具体的比例调整逻辑 */ }
    }
}

在上述代码示例中,定义了一个名为 IResizable 的接口,它包含了两个成员:一个方法Resize和一个属性Scale。方法Resize没有具体的实现,只有方法签名,即方法名和参数列表。属性Scale包含了一个get和一个set访问器。Rectangle类使用implements关键字实现了IResizable接口,并提供了接口中定义的Resize方法和Scale属性的具体实现。

10.2 接口的实现和继承

接口继承规则:
实现过程必须在实现接口的类中完成;
类继承具有单根性,接口可多重继承;
父接口也成为该接口的显示基接口;
接口多重继承时,派生接口名与父接口名用冒号隔开,多个父接口之间用逗号隔开;
接口成员之间不能同名;继承的成员不用在声明。

【示例代码】
老鹰(eagle)、麻雀(sparrow)、鸵鸟(ostrich)都是鸟类(birds),根据三者的共性提取出鸟类作为父类,但是它们都有各自特点:
老鹰吃小鸡,麻雀吃粮食,驼鸟吃青草;
老鹰和麻雀都会飞,但是鸵鸟不会;
新增一个天鹅(swan),天鹅吃鱼也会飞;
再新增一个气球(balloon),它会飞但是不是鸟类( 只能继承接口)
1、Bird

    abstract class Bird
    {
   
        public abstract void Eat();
    }

2、接口IFlyable

   interface IFlyable
    {
   
        void Fly();
    }

3、

    //定义Ealge类,继承Bird类和IFlyable接口
    class Eagle:Bird,IFlyable
    {
   
        public override void Eat()
        {
   
            Console.WriteLine("这是老鹰,老鹰吃小鸡。");
        }
        //接口的实现过程需要在实现接口的类中进行实现
        public void Fly()
        {
   
            Console.WriteLine("这是老鹰,老鹰会飞。");
        }
    }
    //定义Sparrow 类,继承Bird类和IFlyable接口
    class Sparrow : Bird,IFlyable
    {
   
        public override void Eat()
        {
   
            Console.WriteLine("这是麻雀,麻雀吃粮食。");
        }
        public void Fly()
        {
   
            Console.WriteLine("这是麻雀,麻雀会飞。");
        }
    }
	//定义Ostrich 类,继承Bird类
    class Ostrich : Bird
    {
   
        public override void Eat()
        {
   
            Console.WriteLine("这是鸵鸟,鸵鸟吃青菜。");
        }
    }
	//定义Swan类,继承Bird类和IFlyable接口
    class Swan:Bird,IFlyable
    {
   
        public override void Eat()
        {
   
            Console.WriteLine("这是天鹅,天鹅吃鱼。");
        }

        public void Fly()
        {
   
            Console.WriteLine("这是天鹅,天鹅会飞。");
        }
    }
    	//定义Balloon类,继承IFlyable接口
        class Balloon:IFlyable
    {
   
        public void Fly()
        {
   
            Console.WriteLine("这是气球,气球会飞。");
        }
    }

4、主体代码

    class Program
    {
   
        static void Main(string[] args)
        {
   
            //老鹰(eagle)、麻雀(sparrow)、鸵鸟(ostrich)都是鸟类(birds)
            //根据三者的共性提取出鸟类作为父类
            //但是它们都有各自特点:老鹰吃小鸡,麻雀吃粮食,驼鸟吃青草。
            //老鹰和麻雀都会飞,但是鸵鸟只会奔跑
            //新增一个天鹅(swan),天鹅吃鱼也会飞
            //再新增一个气球(balloon),它会飞但是不是鸟类 - 只能继承接口
            Bird[] birds = {
    new Eagle(), new Sparrow(), new Ostrich(), new Swan() };
            foreach (Bird outbird in birds)
                outbird.Eat();
                
            IFlyable[] flys = {
    new Sparrow(), new Eagle(),new Swan(),new Balloon() };
            foreach (IFlyable outfly in flys)
                outfly.Fly();
                
            Console.ReadKey();
        }
    }

运行程序:

这是老鹰,老鹰吃小鸡。
这是麻雀,麻雀吃粮食。
这是鸵鸟,鸵鸟吃青菜。
这是天鹅,天鹅吃鱼。
这是麻雀,麻雀会飞。
这是老鹰,老鹰会飞。
这是天鹅,天鹅会飞。
这是气球,气球会飞。

10.3 显式实现接口

隐式地实现接口:
既可用接口调用方法,也可以用具体的类调用方法。

显示的实现接口,实现接口的方法前不能用访问修饰符public,必须显示指定接口名称:

void 接口名称.方法()
{
   
	//方法体
}

显式实现接口作用:指定方法是哪个接口中的
显式实现接口调用:无法通过具体类来调用,只能通过接口调用

【代码示例】
1、父类 - Bird

    abstract class Bird
    {
   
        public abstract void Eat();
    }

2、接口 - IFlyable1、IFlyable2

    interface IFlyable1
    {
   
        void Fly();
    }
        interface IFlyable2
    {
   
        void Fly();
    }

3、Eagle类 - 继承Bird类和IFlyable1接口

    class Eagle:Bird,IFlyable1
    {
   
        public override void Eat()
        {
   
            Console.WriteLine("这是老鹰,老鹰吃小鸡。");
        }
        public void Fly()
        {
   
            Console.WriteLine("(隐式显示)这是老鹰,老鹰会飞。");
        }
    }

调用:

    class Program
    {
   
        static void Main(string[] args)
        {
   
            //接口实例化
            //隐式地实现接口
            //接口调用
            IFlyable1 eagle1 = new Eagle();
            eagle1.Fly();
            //具体类调用
            Eagle eagle2 = new Eagle();
            eagle2.Eat();
            eagle2.Fly();
            
            Console.ReadKey();
        }
    }

运行程序:

(隐式显示)这是老鹰,老鹰会飞。
这是老鹰,老鹰吃小鸡。
(隐式显示)这是老鹰,老鹰会飞。

运行结果可知:
当类中是隐式接口时,不论采用接口调用或具体类调用结果都是隐式显示。

4、Balloon类 - 同时继承IFlyable1和IFlyable2接口

    class Balloon:IFlyable1,IFlyable2
    {
   
        //显式实现接口
        void IFlyable1.Fly()
        {
   
            Console.WriteLine("这是继承的IFlyable1中的Fly()方法。");
        }
        void IFlyable2.Fly()
        {
   
            Console.WriteLine("这是继承的IFlyable2中的Fly()方法。");
        }
    }

调用:

    class Program
    {
   
        static void Main(string[] args)
        {
   
            //接口实例化
            //显式地实现接口 - 指定方法是哪个接口中的
            //接口调用
            IFlyable1 balloon1 = new Balloon();
            balloon1.Fly();
            IFlyable2 balloon2 = new Balloon();
            balloon2.Fly();
            //具体类调用 - 无法通过具体类来调用,只能通过接口调用
            //Balloon balloon3 = new Balloon();
            //balloon3.Fly();
            
            Console.ReadKey();
        }
    }

运行程序:

这是继承的IFlyable1中的Fly()方法。
这是继承的IFlyable2中的Fly()方法。

继承多个具有相同方法时候,需采用显示实现接口,显式实现接口目的是指定方法是哪个接口中的,其调用时无法通过具体类来调用,只能通过接口调用。

5、Sparrow类 - 继承Bird类和IFlyable1(隐式实现、显示实现同时存在)

    class Sparrow:Bird,IFlyable1
    {
   
        public override void Eat()
        {
   
            Console.WriteLine("这是ma麻雀,麻雀吃粮食。");
        }
        public void Fly()
        {
   
            Console.WriteLine("(隐式实现)这是麻雀,麻雀会飞。");

        }
        void IFlyable1.Fly()
        {
   
            Console.WriteLine("(显式实现)这是麻雀,麻雀会飞。");
        }
    }

调用:

    class Program
    {
   
        static void Main(string[] args)
        {
   
            //同时显示和隐式实现接口方法 - 类中同时存在隐式接口和显示接口
            IFlyable1 sparrow1 = new Sparrow();
            sparrow1.Fly();     //显示实现 - (隐式实现)这是麻雀,麻雀会飞。
            Sparrow sparrow2 = new Sparrow();
            sparrow2.Fly();     //隐式实现 - (显式实现)这是麻雀,麻雀会飞。

            Console.ReadKey();
        }
    }

运行程序:

(显式实现)这是麻雀,麻雀会飞。
(隐式实现)这是麻雀,麻雀会飞。

由结果可知:
当类中同时存在隐式接口和显示接口时,用接口调用是显示实现,用具体类调用是隐式实现。
注意与类中只有隐式接口的区别:
类中只有隐式接口时,不论采用接口调用或具体类调用结果都是隐式显示。

10.4 抽象类与抽象方法声明

在C#中,抽象类和抽象方法是面向对象编程中的重要概念。它们用于定义和组织具有共同特征和行为的类和方法。下面分别介绍抽象类和抽象方法的声明。

1、抽象类(Abstract Class):
抽象类是一种不能被实例化的类,只能被继承。抽象类用于作为其他类的基类,提供一种通用的模板或契约,并定义了一些抽象方法和/或具体方法。抽象类通过将其成员声明为抽象方法或具体方法来定义其行为。
抽象类的声明格式如下:

abstract class ClassName
{
   
    // 抽象方法和具体方法的声明
}

抽象类可以包含以下成员:
① 抽象方法(Abstract Method):抽象方法是一种没有实现体的方法,只包含方法签名。它没有具体的实现代码,需要在派生类中进行实现。
② 具体方法(Concrete Method):具体方法是在抽象类中有具体实现的方法。它提供了类的通用行为,可以被派生类直接使用。
使用抽象类的主要目的是为了实现继承和多态性的概念,将一组相关的类进行抽象和归类,从而提高代码的可维护性和可扩展性。

2、抽象方法(Abstract Method):
抽象方法是一种没有实现体的方法,只包含方法签名。抽象方法必须定义在抽象类中,用于在派生类中进行具体实现。

抽象方法的声明格式如下:

abstract returnType MethodName(parameters);

抽象方法只包含方法的声明部分,没有具体的实现代码。它用于定义一个方法的特征和接口,而不涉及具体的实现细节。抽象方法必须使用abstract关键字进行声明,并且必须在抽象类中进行定义。

派生类必须实现父类中的抽象方法,并提供具体的实现代码。如果派生类没有实现抽象方法,那么派生类也必须声明为抽象类。

需要注意:
① 抽象类使用abstract关键字进行声明,并且不能被实例化。
② 抽象方法使用abstract关键字进行声明,并且没有方法体。
③ 抽象方法必须在抽象类中声明,在派生类中必须实现(重写)抽象方法。
④ 抽象类可以包含普通成员(属性、方法等),不限制具体实现。

【代码示例】

    //声明抽象类  必须包含abstract关键字
    //抽象类的声明就是为了实现派生或者继承,因此不可将其私有化
    public abstract class Class1
    {
   
        //声明抽象方法  必须包含abstract关键字  且不能有方法体
        public abstract void Method1();
        public abstract void Method2();

        //抽象类中课可以包含非抽象方法
        public void Add(int a,int b)
        {
   
            Console.WriteLine(a+b);
        }
    }

    //如果派生类没有实现所有的抽象方法,则该派生类也必须声明为抽象类
    public abstract class Class2 : Class1
    {
   
        public override void Method1()
        {
   
            Console.WriteLine("这是Class2继承Class1中的Method1。");
        }
    }

抽象类非抽象类
抽象类前有关键字 abstract,抽象方法不提供任何实际实现;没有则为非抽象类

注意:
抽象方法必须在抽象类中声明;
不能使用staticprivatevirtual修饰符;
方法不能有任何可执行程序,方法体也不行
重写抽象方法时采用override抽象类不能实例化,必须通过继承由派生实现其抽象方法,因此不能new,不能sealed
如果派生类没有实现所有的抽象方法,则该派生类也必须声明为抽象类。
如果一个非抽象类从抽象类中派生,则其必须通过重载来实现所有继承而来的抽象成员。

10.5 抽象方法(abstract)方法与虚方法(virtual)方法

在C#中,抽象方法(abstract method)和虚方法(virtual method)都是用于实现多态性的机制,但它们在用法和行为上有一些异同点。

相同点:
① 都用于实现多态性:抽象方法和虚方法都是面向对象编程中的多态性的实现方式。它们允许派生类在继承基类的同时,对方法进行不同的实现,实现方法的重写。
② 都只存在于父类中:抽象方法和虚方法都属于父类中的成员,子类可以重写这些方法,但不可以直接调用父类的实现。
③ 都可以通过override来实现对原有方法的重写。

不同点:
① 实现方式不同:抽象方法必须声明在抽象类中,没有具体的实现,需要在派生类中进行实现。而虚方法是在父类中有具体的实现,并使用virtual关键字进行声明,可以选择性地在派生类中进行重写。
② 强制实现 vs 可选实现:抽象方法强制派生类对其进行实现,如果派生类没有实现抽象方法,那么派生类本身也必须声明为抽象类。而虚方法在派生类中实现是可选的,派生类可以选择是否重写虚方法。
③ 调用方法不同:抽象方法只能通过派生类对象的实例进行调用,因为抽象类不能被实例化。而虚方法可以通过父类对象的实例和派生类对象的实例进行调用。

【程序示例】 通过抽象方法与虚方法实现计算x的y次幂的值。
抽象方法:

    abstract class Pow
    {
   
        public abstract void PowMethod(int x, int y);
    }
    
    class PowB : Pow
    {
   
        public override void PowMethod(int x, int y)
        {
   
            int pow = 1;
            for(int i = 1; i <= y; i++)
            {
   
                pow *= x;
            }
            Console.Write("abstract方法类中PowB派生类继承Pow方法,自定义计算幂:");
            Console.WriteLine("{0}的{1}次幂是{2}",x,y,pow);
        }      
    }

    class PowC : Pow
    {
   
        public override void PowMethod(int x, int y)
        {
   
            Console.Write("abstract方法类中PowC派生类继承Pow方法,系统函数计算:");
            Console.WriteLine("{0}的{1}次幂是{2}", x, y, System.Math.Pow(x, y));
        }
    }
    class Program
    {
   
        static void Main(string[] args)
        {
   
            PowB pow1 = new PowB();
            pow1.PowMethod(2, 3);

            PowC pow2 = new PowC();
            pow2.PowMethod(2, 3);

            Console.ReadKey();
        }
    }

运行程序:

abstract方法类中PowB派生类继承Pow方法,自定义计算幂:23次幂是8
abstract方法类中PowC派生类继承Pow方法,系统函数计算:23次幂是8

虚方法:

    class Pow
    {
   
        public virtual void PowMethod(int x, int y)
        {
   
            int pow = 1;
            for (int i = 1; i <= y; i++)
            {
   
                pow *= x;
            }
            Console.Write("virtual方法类中Pow 基类定义的Pow方法,自定义计算幂:");
            Console.WriteLine("{0}的{1}次幂是{2}", x, y, pow);
        }
    }

    class PowB:Pow
    {
   
        public override void PowMethod(int x, int y)
        {
   
            Console.Write("virtual方法类中PowB派生类继承Pow方法,系统函数计算:");
            Console.WriteLine("{0}的{1}次幂是{2}", x, y, System.Math.Pow(x, y));
        }
    }
    class Program
    {
   
        static void Main(string[] args)
        {
   
            Pow pow1 = new Pow();
            pow1.PowMethod(2, 3);

            PowB pow2 = new PowB();
            pow2.PowMethod(2, 3);

            Console.ReadKey();
        }
    }

运行程序:

virtual方法类中Pow 基类定义的Pow方法,自定义计算幂:23次幂是8
virtual方法类中PowB派生类继承Pow方法,系统函数计算:23次幂是8

10.6 密封类与密封方法

在C#中,密封类(sealed class)和密封方法(sealed method)用于限制继承和重写的行为。它们可以被用来避免进一步派生和重写,提供更严格的类和方法的定义。

1、密封类(Sealed Class):
密封类是一种用于限制继承的特殊类。当一个类被声明为密封类时,它不能被其他类继承。密封类用于表示某个类的最终实现,禁止通过派生创建新的子类。

密封类的声明格式如下:

sealed class ClassName
{
   
    // 类的定义
}

要注意以下几点:

① 密封类必须是最终类,可以继承其他类,但不能被其他类继承;不能作为基类供其他类进行派生。
② 密封类可以包含字段、属性、方法、事件等成员,具体根据需求定义即可;可以包含成员和方法,可以具有抽象成员、虚成员或普通成员。
③ 密封类与抽象类是相反的概念,抽象类用于表示可以被继承的类,而密封类用于表示不能被继承的类。
④ 使用密封类的主要目的是为了避免类被进一步继承,强制类作为最终实现,从而确保类的完整性和稳定性。例如,System.String类就是一个密封类,它不能被继承。

2、密封方法(Sealed Method):
密封方法是一种用于限制重写的方法。当一个方法被声明为密封方法时,它不能被派生类进一步重写。密封方法用于表示某个方法的最终实现,禁止在派生类中重新定义该方法。

密封方法的声明格式如下:

class ClassName
{
   
    sealed returnType MethodName(parameters)
    {
   
        // 方法体
    }
}

要注意以下几点:
① 密封方法必须是已重写的方法,并在声明时使用sealed关键字进行标记。
② 密封方法只能在派生类中重写一次,而派生类中的任何子类都无法重写密封方法。
③ 密封方法不能在非虚方法或非重写方法中在C#中,密封类(sealed class)和密封方法(sealed method)是用于限制类和方法的继承和重写的关键字。
④ 使用密封方法的主要目的是为了禁止在派生类中进一步重写某个方法,确保方法的逻辑完整性和稳定性。

【密封类的示例】

sealed class MySealedClass
{
   
    public void SomeMethod()
    {
   
        Console.WriteLine("This is a method in a sealed class.");
    }
}

说明:
MySealedClass是一个密封类,它声明了一个方法SomeMethod( )。这个类不能被其他类继承,因此不能有子类。

【密封方法的示例】

class MyBaseClass
{
   
    public virtual void SomeMethod()
    {
   
        Console.WriteLine("This is the base method.");
    }
}

class MyDerivedClass : MyBaseClass
{
   
    sealed override void SomeMethod()
    {
   
        Console.WriteLine("This is a sealed method in a derived class.");
    }
}

10.7 接口综合运用实践

多态的实现方法:
①虚方法: 可以抽象出一个类,并且这个类需要有实现
②抽象类: 可以抽象出一个父类,但是抽象类需要有一个方法且实现方法的途径不确定,不需要父类创建对象
③接口: 不能抽象出一个父类,但是可以找他们的共同点,也就是共同行为、能力即共同的属性和方法,接口的表明是一种能力,一种规范

【实例】 真猫不会说话,Tom猫会说话,Kitty猫会说话。
分析:
① 如果采用虚方法定义一个 的基类,需要定义说话的方法,但是真猫不能说话 所以无法派生继承
② 需要定义一个父类定义说话的方法,抽象不能父类中定义方法,所以不乏实现
③ "会"与“不会”说话是一种能力,可以采用接口实现
程序代码:

    //定义接口
    interface ISaywords
    {
   
        void Say();
    }
    //定义RealCat、TomCat和KittyCat类
    class RealCat
    {
   
        public void Say()
        {
   
            Console.WriteLine("我是真猫子,我不会说话,喵喵喵。");
        }
    }
    class TomCat:ISaywords
    {
   
        public void Say()
        {
   
            Console.WriteLine("我是Tom猫,我会说话,TomTom。");
        }
    }
    class KittyCat:ISaywords
    {
   
        public void Say()
        {
   
            Console.WriteLine("我是Kitty猫,我会说话,KittyKitty。");
        }
    }
    class Program
    {
   
        static void Main(string[] args)
        {
   
            RealCat cat = new RealCat();
            cat.Say();
            
            ISaywords[] cats = {
    new TomCat(),new KittyCat()};
            foreach (ISaywords outcat in cats)
                outcat.Say();

            Console.ReadKey();
        }
    }

运行程序:

我是真猫子,我不会说话,喵喵喵。
我是Tom猫,我会说话,TomTom。
我是Kitty猫,我会说话,KittyKitty。

相关推荐

  1. C# 抽象接口

    2023-12-28 10:36:08       36 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-28 10:36:08       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-28 10:36:08       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-28 10:36:08       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-28 10:36:08       18 阅读

热门阅读

  1. 设计模式之状态模式

    2023-12-28 10:36:08       41 阅读
  2. Elasticsearch 常用 REST API 之集群APIs

    2023-12-28 10:36:08       31 阅读
  3. 【前端框架】NPM概述及使用简介

    2023-12-28 10:36:08       35 阅读
  4. 如何解决服务器CA证书过期的问题

    2023-12-28 10:36:08       41 阅读
  5. centos 7.9 安装 qt5.15.11

    2023-12-28 10:36:08       47 阅读
  6. 【后端】拷贝数据字典

    2023-12-28 10:36:08       38 阅读
  7. C# 基于事件的观察者模式

    2023-12-28 10:36:08       33 阅读
  8. Django信号机制源码分析(观察者模式)

    2023-12-28 10:36:08       40 阅读
  9. 基数(Radix)排序

    2023-12-28 10:36:08       33 阅读