目录
一、抽象类
1.示例
二、抽象方法
三、接口
1.示例
2.内联表达治愈警告CA1859
(1)传统程序书写源码
(2)内联后的源码
四、多重继承
1.示例
五、显式接口成员实现
1.示例
六、总结
赠人玫瑰,手有余香, 留下心得,换点流量。
一、抽象类
一般将父类定义为抽象类,需要使用这个父类进行继承与多态处理。C#中声明抽象类时需要使用abstract关键字,声明抽象类时,除abstract关键字、class关键字和类名外,其他的都是可选项。
抽象类除了被继承之外没有任何意义。
1.示例
//抽象类、抽象方法
namespace _01
{public abstract class MyClass{private string id = "";private string name = "";/// <summary>/// 编号属性及实现/// </summary>public string ID{get{return id;}set{id = value;}}/// <summary>/// 姓名属性及实现/// </summary>public string Name{get{return name;}set{name = value;}}/// <summary>/// 抽象方法,用来输出信息/// </summary>public abstract void ShowInfo();}public class DriveClass : MyClass //继承抽象类{/// <summary>/// 重写抽象类中输出信息的方法/// </summary>public override void ShowInfo(){Console.WriteLine(ID + " " + Name);}}class Program{/// <summary>/// 内联/// </summary>static void Main(string[] args){DriveClass driveclass = new(); //实例化派生类,内联前用派生类对象实例化抽象类 ((DriveClass)driveclass).ID = "BH0001"; //使用抽象类对象访问抽象类中的编号属性((DriveClass)driveclass).Name = "TM"; //使用抽象类对象访问抽象类中的姓名属性((DriveClass)driveclass).ShowInfo(); //使用抽象类对象调用派生类中的方法Console.WriteLine("------------");driveclass.ID = "BH0001"; //派生类直接实例化driveclass.Name = "TM";driveclass.ShowInfo();Console.Read();}}
}//结果:
/*BH0001 TM
---------------
BH0001 TM*/
二、抽象方法
在抽象类中声明方法时,如果加上abstract关键字,则为抽象方法。
使用abstract关键字定义的类称为抽象类,而使用这个关键字定义的方法称为抽象方法,抽象方法没有方法体,这个方法本身没有任何意义,除非它被重写,而承载这个抽象方法的抽象类就必须被继承。反之,如果声明一个抽象的方法,就必须将承载这个抽象方法的类定义为抽象类,不可能在非抽象类中获取抽象方法。只要类中有一个抽象方法,此类就被标记为抽象类。
抽象类被继承后需要实现其中所有的抽象方法,也就是保证相同的方法名称、参数列表和相同返回值类型创建出非抽象方法,当然也可以是抽象方法。继承抽象类的所有子类需要将抽象类中的抽象方法进行覆盖。
声明抽象方法时需要注意以下两点:
☑ 抽象方法必须声明在抽象类中。
☑ 声明抽象方法时,不能使用virtual、static和private修饰符。
抽象方法声明引入了一个新方法,但不提供该方法的实现,由于抽象方法不提供任何实际实现,因此抽象方法的方法体只包含一个分号。
当从抽象类派生一个非抽象类时,需要在非抽象类中重写抽象方法,以提供具体的实现,重写抽象方法时使用override关键字。
三、接口
接口是抽象类的延伸,可以将它看作是纯粹的抽象类,接口中的所有方法都没有方法体。接口可由方法、属性、事件和索引器或这4种成员类型的任何组合构成,但不能包含字段,也不能设置这些成员的具体值,即,只能定义,不能给它们赋值。 接口可以继承其他接口。
接口具有以下特征:
☑ 接口类似于抽象基类:继承接口的任何非抽象类型都必须实现接口的所有成员。
☑ 不能直接实例化接口。
☑ 接口可以包含事件、索引器、方法和属性。
☑ 接口不包含方法的实现。
☑ 类和结构可从多个接口继承。
☑ 接口自身可从多个接口继承。
1.示例
// 接口
namespace _02
{interface IMyInterface{/// <summary>/// 编号(可读可写)/// </summary>string ID{get;set;}/// <summary>/// 姓名(可读可写)/// </summary>string Name{get;set;}/// <summary>/// 显示定义的编号和姓名/// </summary>void ShowInfo();}class Program : IMyInterface //继承自接口{string id = "";string name = "";/// <summary>/// 编号/// </summary>public string ID{get{return id;}set{id = value;}}/// <summary>/// 姓名/// </summary>public string Name{get{return name;}set{name = value;}}/// <summary>/// 显示定义的编号和姓名/// </summary>public void ShowInfo(){Console.WriteLine("编号\t 姓名");Console.WriteLine(ID + "\t " + Name);}/// <summary>/// 注释掉的部分,警告CA1859/// 内联临时变量后,警告消失/// </summary>static void Main(string[] args){Program program = new(); //实例化Program类对象//IMyInterface Imyinterface = program; //使用派生类对象实例化接口ImyInterface//Imyinterface.ID = "TM"; //为派生类中的ID属性赋值//Imyinterface.Name = "C#从入门到精通"; //为派生类中的Name属性赋值//Imyinterface.ShowInfo();((IMyInterface)program).ID = "TM"; //为派生类中的ID属性赋值((IMyInterface)program).Name = "C#从入门到精通";//为派生类中的Name属性赋值((IMyInterface)program).ShowInfo(); //调用派生类中方法显示定义的属性值Console.WriteLine("----------------------"); //用子类派生类直接实例化program.ID = "TM";program.Name = "C#从入门到精通";program.ShowInfo();Console.Read();}}
}//运行结果:
/*
编号 姓名
TM C#从入门到精通
----------------------
编号 姓名
TM C#从入门到精通*/
2.内联表达治愈警告CA1859
上述示例中,传统的Main()方法中,先实例化派生类Program,再用派生对象实例化接口,然后用接口的属性为派生类属性赋值。这是.NET Framework 4.8之前的做法,其缺点是程序表达晦涩,不易读,继承关系不明显。
在.NET 7.0、.NET 8.0下不建议这样表达,会提示警告CA1859。VS2022.NET 8.0快速重构建议给出的意见是用内联临时变量的方法,直接表达继承关系,使得程序简洁易读,继承关系清晰明了。
严重性 | 代码 | 说明 | 项目 | 文件 | 行 | 禁止显示状态 |
消息 | CA1859 | 将变量“_Imyinterface”的类型从“_02.IMyInterface”更改为“_02.Program”,以提高性能 | 02 | E:\C#_TM\chapter17\02\Program.cs | 72 | 活动 |
(1)传统程序书写源码
static void Main(string[] args)
{Program program = new(); //实例化Program类对象IMyInterface Imyinterface = program; //使用派生类对象实例化接口ImyInterfaceImyinterface.ID = "TM"; //为派生类中的ID属性赋值Imyinterface.Name = "C#从入门到精通"; //为派生类中的Name属性赋值Imyinterface.ShowInfo(); //调用派生类中方法显示定义的属性值Console.WriteLine("------------------");//用子类派生类直接实例化program.ID = "TM";program.Name = "C#从入门到精通";program.ShowInfo();Console.Read();
}
(2)内联后的源码
static void Main(string[] args){Program program = new(); //实例化Program类对象//使用派生类对象实例化接口 ((IMyInterface)program).ID = "TM"; //为派生类中的ID属性赋值((IMyInterface)program).Name = "C#从入门到精通"; //为派生类中的Name属性赋值((IMyInterface)program).ShowInfo(); //调用派生类中方法显示定义的属性值Console.WriteLine("----------------------"); //用子类派生类直接实例化program.ID = "TM";program.Name = "C#从入门到精通";program.ShowInfo();Console.Read();}
内联的表达可以隐藏(舍去) ,结果是一样的,只是继承关系不再明显可见了。
后面的例子中都会出现CA1859警告,其处理方法同此例,不再重复讲述。
四、多重继承
可以只继承一个接口,接口也可以多重继承,使用多重继承时,要继承的接口之间用逗号“,”分隔。
1.示例
// 继承多个接口
namespace _03
{interface IPeople{/// <summary>/// 姓名/// </summary>string Name{get;set;}/// <summary>/// 性别/// </summary>string Sex{get;set;}}interface ITeacher : IPeople //继承公共接口{/// <summary>/// 教学方法/// </summary>void Teach();}interface IStudent : IPeople //继承公共接口{/// <summary>/// 学习方法/// </summary>void Study();}class Program : IPeople, ITeacher, IStudent//多接口继承{string name = "";string sex = "";/// <summary>/// 姓名/// </summary>public string Name{get{return name;}set{name = value;}}/// <summary>/// 性别/// </summary>public string Sex{get{return sex;}set{sex = value;}}/// <summary>/// 教学方法/// </summary>public void Teach(){Console.WriteLine(Name + " " + Sex + " 教师");}/// <summary>/// 学习方法,学习内联的表达/// 内联前,用派生类对象实例化接口ITeacher/// 内联前,用派生类对象实例化接口IStudent/// 舍去内联的表达后效果一样,内联的存在使得继承关系清晰明了/// </summary>public void Study(){Console.WriteLine(Name + " " + Sex + " 学生");}static void Main(string[] args){Program program = new(); //实例化类对象,内联表达((ITeacher)program).Name = "TM";((ITeacher)program).Sex = "男";((ITeacher)program).Teach();((IStudent)program).Name = "C#";((IStudent)program).Sex = "男";((IStudent)program).Study();Console.WriteLine("--舍去内联--");program.Name = "TM";program.Sex = "男";program.Teach();program.Name = "C#";program.Sex = "男";program.Study();Console.Read();}}
}//运行结果:
/*
TM 男 教师
C# 男 学生
--舍去内联--
TM 男 教师
C# 男 学生 */
五、显式接口成员实现
如果类实现两个接口,并且这两个接口包含具有相同签名的成员,那么在类中实现该成员将导致两个接口都使用该成员作为它们的实现。然而,如果两个接口成员实现不同的功能,则可能会导致其中一个接口的实现不正确或两个接口的实现都不正确,这时可以显式地实现接口成员,即创建一个仅通过该接口调用并且特定于该接口的类成员。显式接口成员实现是使用接口名称和一个句点命名该类成员来实现的。
显式接口成员实现中不能包含访问修饰符、abstract、virtual、override或static修饰符。
显式接口成员属于接口的成员,而不是类的成员,因此,不能使用类对象直接访问,只能通过接口对象来访问。
使用场合:当继承多个接口的类中欲实现来自各自接口的同名方法赋予不同的功能时。
1.示例
// 显式接口成员实现
namespace _04
{interface IMyInterface1{/// <summary>/// 求和方法/// </summary>int Add();}interface IMyInterface2{/// <summary>/// 求和方法/// </summary>int Add();}/// <summary>/// 显式接口成员实现/// </summary>class MyClass : IMyInterface1, IMyInterface2 //继承接口{/// <summary>/// 求和方法1/// </summary>int IMyInterface1.Add() //显式接口成员实现{int x = 3;int y = 5;return (x + y);}/// <summary>/// 求和方法2/// </summary>int IMyInterface2.Add() //显式接口成员实现{int x = 3;int y = 5;int z = 7;return (x + y + z);}}class Program{static void Main(string[] args){MyClass myclass = new(); //实例化接口继承类的对象IMyInterface1 imyinterface1 = myclass; //使用接口继承类的对象实例化接口Console.WriteLine(imyinterface1.Add()); //使用接口对象调用接口中的方法IMyInterface2 imyinterface2 = myclass; //使用接口继承类的对象实例化接口Console.WriteLine(imyinterface2.Add()); //使用接口对象调用接口中的方法Console.WriteLine("----内联表达----"); //使用内联表达,是不是更清晰易读?Console.WriteLine(((IMyInterface1)myclass).Add()); Console.WriteLine(((IMyInterface2)myclass).Add());Console.Read();}}
}//运行结果:
/*
8
15
----内联表达----
8
15 */
内联表达,用最少的语句,实现最清晰的继承关系表达,是不是你的最爱?
六、总结
抽象类和接口都包含可以由派生类继承的成员,它们都不能直接实例化,但可以声明它们的变量。如果这样做,就可以使用多态性把继承这两种类型的对象指定给它们的变量。
抽象类和接口这两种类型用于完全不同的目的。抽象类主要用作对象系列的基类,共享某些主要特性,例如共同的目的和结构。接口则主要用于类,这些类在基础水平上有所不同,但仍可以完成某些相同的任务。
抽象类和接口的区别主要有以下几点:
☑ 它们的派生类只能继承一个基类,即只能直接继承一个抽象类,但可以继承任意多个接口。
☑ 抽象类中可以定义成员的实现,但接口中不可以。
☑ 抽象类中可以包含字段、构造函数、析构函数、静态成员或常量等,接口中不可以。
☑ 抽象类中的成员可以是私有的(只要它们不是抽象的)、受保护的、内部的或受保护的内部成员(受保护的内部成员只能在应用程序的代码或派生类中访问),但接口中的成员必须是公共的。
☑ 派生类必须继承抽象类的全部。派生类可以选择是否继承接口。
☑ 抽象类使得所有派生类大同小异,继承多个接口则使得派生类各有不同。