文章目录
.Net面向对象(类)
为什么要学习面向对象编程?
面向过程编程,很难解决复杂业务逻辑和适应业务需求的编号,面向对象能够将程序很好的“模块化设计”,清晰的“分层组合”,方便的“业务逻辑”。
一. 类
C#是面向对象的语言,使用类来进行抽象。在 C# 中,类是引用类型的。
类是对象在面向对象编程语言中的反映。类从客观事务中抽象和总结出来的“蓝图”。 类描述了一系列在概念上有相同含义的对象,并为这些对对象统一定义了各种成员。 类是一种数据结构,可以包含数据成员(常量和域)、函数成员(方法、属性、事件、索引器、构造函数和析构函数)和嵌套类型等。
我们可以创建类的实例(instance),这个实例也被称为对象(object),我们可以通过类和对象来编程。
1.1. 类的定义
类的定义需要使用 class 关键字,语法格式如下:
访问修饰符 class 类名
{
//成员变量 可多个,可以私有,可以公开
// 成员属性 可多个
private 数据类型 字段名;
public 数据类型 属性名
{
get {return 字段;}
set {字段=value;}
}
// 成员方法 可以一个或多个
访问修饰符 返回值类型 方法名(参数列表......)
{
// 方法体
}
}
语法说明如下:
-
访问修饰符,用来指定类或类中成员的访问规则,可以忽略不写,如果没有指定,则使用默认的访问权限修饰符,类的默认访问修饰符是 internal,类中成员的默认访问修饰符是 private;
-
类名,用Pascal 命名规范,如StudentInfo;
-
数据类型,用来指定成员属性或变量的数据类型;
-
成员变量:类似于变量名;
-
返回值类型,用来指定成员方法的返回值类型;
public class Course
{
// 字段
private int id;
// 属性 完整定义属性
public int CourseId
{
get{return id;}
set{id=value;}
}
// 属性 自动属性
public string CourseName{get;set;}
// 没有访问修饰符的默认是私有的
public void ShowCourse()
{
Console.WriteLine("课程编号:"+CourseId+"; 课程名称:"+courseName);
}
}
1.2. 对象
类和对象是两个不同的概念,类决定了对象的类型,但不是对象本身。另外,类是在开发阶段创建的,而对象则是在程序运行期间创建的。我们可以将对象看作是基于类创建的实体,所以对象也称为类的实例。
要创建一个类的实例需要使用 new 关键字,假如我们创建了一个名为 Course的类,若要创建这个类的对象,语法格式如下:
Course course=new Course();
前面的 Course是要创建的对象的类型,而 course则是一个变量,它引用了 Course类实例(即Course类的对象)的内存地址。
new 在这里的作用主要是在程序运行时为类的实例分配内存。
我们还可以像创建变量那样只创建一个 指定类型的变量,而不使用 new 关键字实例化 Course这个类,
Course course01;
不过不建议这样写,因为这个声明的变量只是一个 Course类型的普通变量,它并没有被赋值,所以不能使用 course01来访问对象中的属性和方法。如果一定要通过这个种方式声明,可以将一个已经实例化的对象赋给它。
course01=course;
不过,这样,course01与course都指向同一个Course对象,如果不管用哪个变量去操作对象,都会影响另一个变量中的内容。
创建好对象后,就可以通过对象访问类中的成员,用(.)运算符来访问。
course.CourseId=101; // 访问属性,修改属性值
course.ShowCourse(); // 调用成员方法
1.3. 类和对象的使用
Course course01=new Course();
Course course02=new Course();
course01.CourseId=101;
course01.CourseName="客户端基础就业课程";
course01.ShowCourse();
course02.CourseId=102;
course02.CourseName="WPF+工业互联进阶课程";
course02.ShowCourse();
1.4. 属性
属性(Property)是类(class)、结构体(structure)和接口(interface)都可以包含的成员,类或结构体中的成员变量称为字段,属性是字段的扩展,使用访问器(accessors)可以读写私有字段的值。
属性没有确切的内存位置,但具有可读写的访问器。例如类名为 Course的类,其中包含 id 私有字段,我们不能在类的范围以外直接访问这些字段,但是可以访问这些私有字段对应的属性。
1.4.1. 访问器
属性访问器有两种,分别是 get和 set 属性访问器。其中 get 访问器用来返回属性的值,set 访问器用来为属性设置新值。
在声明访问器时可以仅声明其中一个,也可以两个访问器同时声明。
// 定义CourseId属性
private int id;
public int CourseId
{
get{return id;}
set{id=value;}
}
//CourseName属性 自动属性
public string CourseName{get;set;}
// 类中属性的使用:
Course course01=new Course();
course01.CourseId=101;
course01.CourseName="客户端基础就业课程";
course01.ShowCourse();
1.4.2. 抽象属性
抽象类中可以拥有抽象属性,这些属性会在派生类中实现。
// 抽象属性
public abstract string Name
{
get;
set;
}
1.5. 抽象类
C# 中使用 abstract 关键字来创建,抽象类用于实现部分接口。另外,抽象类包含抽象方法,可以在派生类中实现。
有关抽象类的规则:
-
不能创建一个抽象类的实例;
-
不能在一个抽象类外部声明抽象方法;
-
通过在类定义时使用 sealed 关键字,可以将类声明为密封类,密封类不能被继承,因此抽象类不能声明密封类。
//抽象类
public abstract class People
{
//抽象属性
public abstract int Age
{
get;
set;
}
public abstract void Work();
}
//学生类---People的派生类
public class Student:People
{
private int studentId;
private string studentName;
private int age;
public override int Age
{
get
{
return age;
}
set
{
age = value;
}
}
public Student(int id,string name)
{
studentId=id;
studentName=name;
}
public override void Work()
{
Console.WriteLine("编号:" + studentId + ",姓名:" + studentName + ",年龄:"+Age+" 在学校学习!");
}
}
// Main中调用:
StudentNew student = new StudentNew(101, "李红");
student.Age = 25;
student.Work();
// 也可以用派生类声明对象
Person personClass = new Student();
personClass.Id = 2;
personClass.Name = "ZhangSan";
personClass.Work();
1.6. 静态成员
在 C# 中,我们可以使用 static 关键字声明一个类而不属于特定对象的静态成员,因此不能使用类的对象来访问静态成员。
在类、接口和结构体中都可以使用 static 关键字定义变量、方法、构造函数、类、属性、运算符和事件。
不过,索引器与析构函数不能是静态的。
定义类的某个成员时如果使用 static ,表示该类仅存在此成员的一个实例。无论你创建多少个该类的对象,静态成员只会被创建一次,这个静态成员会被所有对象共享。
1.6.1. 静态属性、静态变量
使用 static 定义的属性称为“静态属性”,定义的变量称为“静态变量”,静态属性或变量直接通过 类名.属性名
直接访问,不需要事先创建类的实例。
静态属性不仅可以在类的内部方法中初始化,还可以直接在类外进行初始化。
public class Teacher
{
//静态变量
public static string courseType;
//静态属性
public static int T_CourseCount{get;set;}
public void AddCourse()
{
T_CourseCount += 1; //内部改变属性值
}
}
// 访问:
Teacher teacher = new Teacher();
Teacher.T_CourseCount = 1;//外部初始化
Teacher.courseType="计算机类";
teacher.AddCourse();
1.6.2. 静态方法
在类中,除了可以定义静态属性,static 关键字还可以用来定义成员方法,使用 static 定义的成员方法称为“静态方法”,静态方法只能访问静态成员,不能访问非静态成员,但方法内部可以声明非静态的局部变量。
public static void ShowCourse()
{
T_CourseCount = 1;
//AddCourse(); //不能访问非静态成员
string classroom = "101教室";
Console.WriteLine("课程数:"+T_CourseCount+"; 教室:"+classroom);
}
1.7. 静态类
在C#中,用static关键字定义的类称为“静态类”。一个类如果定义为静态类,则该类不能被实例化,其类中的成员也必须是静态。
静态类一般用于封装通用处理类,里边封装相关的一系列通用方法,可重用。
静态类有如下特征:
-
静态类中只包含静态成员,可以是静态变量、静态属性、静态方法等等;
-
静态类无法实例化;
-
静态类无法派生子类 ;
-
静态类不能包含实例构造函数;
-
静态类可以包含静态构造函数
//静态类
public static class CommonHelper
{
//静态变量、属性
public static int totalCount=0;
public static decimal totalAmount=0.0m;
//静态方法
public static string GetInfo(ItemInfo itemInfo)
{
return "编号:"+itemInfo.ItemId+", 类别:"+itemInfo.ItemType+", 名称:"+itemInfo.ItemName;
}
}
1.8. 密封类
在C#中,使用sealed关键字修饰的类,称为“密封类”。密封类是无法被继承的。
在面向对象程序的设计中,密封类的主要作用就是不允许最底层的子类被继承。可以保证程序的规范性,安全性。
目前对于大家来说,可能用处不大。以后制作复杂系统或者程序框架时,可能会涉及。
public sealed class ChildClass
{
}
这个ChildClass类,其他类是不能继承于它的。
1.9. 构造函数
在 C# 中,构造函数就是与类(或结构体)具有相同名称的成员函数,它在类中的地位比较特殊,不需要我们主动调用,当创建一个类的对象时会自动调用类中的构造函数。在程序开发的过程中,我们通常使用类中的构造函数来初始化类中的成员属性。
C# 中的构造函数有三种:
-
实例构造函数;
-
静态构造函数;
-
私有构造函数。
1.9.1. 实例构造函数
构造函数是类中特殊的成员函数,它的名称与它所在类的名称相同,没有返回值。当我们使用 new 关键字创建类的对象时,可以使用实例构造函数来初始化类中的任意成员属性或变量。
public class Course
{
//属性 完整定义属性
private int id;
public int CourseId
{
get{return id;}
set{id=value;}
}
//属性 自动属性
public string CourseName{get;set;}
//带参数的构造函数
public Course(int id,string courseName)
{
CourseId=id;
CourseName=courseName;
}
}
当前创建 Course类的对象时,就会调用类中的实例构造函数,我们只需要在实例化对象时将具体的值传递给类中的构造函数即可。
Course course=new Course(105,"高级班课程");
之前,我们没有在类中定义构造函数,其实C#会为这个类隐式的创建一个没有参数的构造函数(无参数构造函数),这个无参的构造函数会在实例化对象时为类中的成员属性设置默认值。在结构体中也是如此,如果没有为结构体创建构造函数,那么 C# 将隐式的创建一个无参数的构造函数,用来将每个字段初始化为其默认值。
若要初始化静态类静态属性,可以使用静态构造函数。
1.9.2. 静态构造函数
静态构造函数用于初始化类中的静态数据或执行仅需执行一次的特定操作。
静态构造函数将在创建第一个实例或引用类中的静态成员之前 自动调用。
静态构造函数具有以下特性:
-
静态构造函数不使用访问修饰符修饰或不具有参数;
-
类或结构体中只能具有一个静态构造函数;
-
静态构造函数不能继承或重载;
-
静态构造函数不能直接调用,仅可以由公共语言运行时 (CLR) 调用;
-
用户无法控制程序中静态构造函数的执行时间;
-
在创建第一个实例或引用任何静态成员之前,将自动调用静态构造函数以初始化类;
-
静态构造函数会在实例构造函数之前运行。
public class Teacher
{
public static int count=0;
public Teacher()
{
count=1;
}
static Teacher()
{
count=3;
}
}
在Program的Main方法中:
Console.WriteLine($"count = {Teacher.count}");
Teacher teacher1 = new Teacher();
Console.WriteLine($"count = {Teacher.count}");
//执行,输出:
count = 3
count = 1
通过输出结果,我们可以看出,会先执行静态构造函数,再执行实例构造函数。
1.9.3. 私有构造函数
私有构造函数是一种特殊的实例构造函数,通常用在只包含静态成员的类中。如果一个类中具有一个或多个私有构造函数而没有公共构造函数的话,那么其他类(除嵌套类外)则无法创建该类的实例。
public class CommonClass
{
private CommonClass(){}
public static double pi=3.14;
}
这么做的好处就是空构造函数可阻止自动生成无参数构造函数。需要注意的是,如果不对构造函数使用访问权限修饰符,则默认是private,即私有构造函数。
// 单例模式
public class RecordInfo
{
private static RecordInfo record = null;
private RecordInfo()
{
}
//返回实例
public static RecordInfo GetObj()
{
if(record == null)
{
record = new RecordInfo();
}
return record;
}
}
RecordInfo record1 = RecordInfo.GetObj();
RecordInfo record2= RecordInfo.GetObj();
1.10. 析构函数
与构造函数类似,C# 中的析构函数也是类中的一个特殊成员函数,主要用于在垃圾回收器回收类实例时执行一些必要的清理操作。
C# 中的析构函数具有以下特点:
-
析构函数只能在类中定义,不能用于结构体;
-
一个类中只能定义一个析构函数;
-
析构函数不能继承或重载;
-
析构函数没有返回值;
-
析构函数是自动调用的,不能手动调用;
-
析构函数不能使用访问修饰符修饰,也不能包含参数。
析构函数的名称同样与类名相同,不过需要在名称的前面加上一个波浪号~
作为前缀,
public class Product:IDisposable
{
// 构造函数
public Product()
{
Console.WriteLine("构造Product对象");
}
//析构函数
~Product()
{
Console.WriteLine("清理工作");
}
// 释放处理
public void Dispose()
{
Console.WriteLine("已释放");
}
}
// 主程序中:
Product product = new Product();
product.Dispose();
1.11. this关键字
在 C# 中,可以使用 this 关键字来表示当前对象,开发中我们可以使用 this 关键字来访问类中的成员属性、变量,也可以用来串联构造函数,可以作为类的索引器,还可以用于在扩展方法中指明要扩展的类型。
public class Product
{
public Product()
{
Console.WriteLine("构造Product对象");
}
private int proId;
public string ProductName{get;set;}
private int[] noArr=new int[5];
// 在构造函数中使用this关键字
public Product(int id,string name)
{
this.proId=id;
this.ProductName=name;
}
//this 串联构造函数
public Product(string name):this()
{
Console.WriteLine("名称:"+name);
Console.WriteLine("通过名称构造Product");
}
public void Show()
{
Console.WriteLine("产品编号:"+proId+"; 名称:"+ProductName);
}
//给noArr赋值
public void SetNos(int[] nos)
{
if(nos.Length==5)
{
for(int i = 0; i < nos.Length; i++)
{
noArr[i] = nos[i];
}
}
}
//索引器
public int this[int index]
{
get
{
if(index>=0&&index<5)
return noArr[index];
return -1;
}
set
{
if(index>=0&&index<5)
noArr[index] = value;
}
}
}
// Main中:
Product product=new Product(10001,"儿童短袖");
product.Show();
Product product1=new Product("春季运动鞋");
//输出结果:
构造Product对象
名称:春季运动鞋
通过名称构造Product
Product product2 = new Product();
product2.SetNos(new int[] { 1002, 1003, 1005, 1006, 1009 });
int no1 = product2[2] = 1010;
int no2 = product2[4] = 1011;
Console.WriteLine(no1+","+no2);
//扩展方法 必须是在静态类中,第一个参数的类型就是要扩展的类型,用this
public static class StringHelper
{
//将整数字符串转换为整型
public static int GetInt(this string str)
{
int reVal = 0;
int.TryParse(str, out reVal);
return reVal;
}
}
//调用
string strInt = "234";
int intVal = strInt.GetInt();//调用扩展方法
1.12. 索引器
索引器(Indexer)是类中的一个特殊成员,它能够让对象以类似数组的形式来访问,使程序看起来更为直观,更容易编写。索引器与属性类似,在定义索引器时也会用到 get 和 set 访问器,但访问属性不需要提供参数,而访问索引器则需要提供相应的参数。
1.12.1. 定义索引器
C# 中属性的定义需要提供属性名称,而索引器则不需要具体名称,而是使用 this 关键字来定义。
索引器类型 this[int index]
{
// get 访问器
get
{
// 返回 index 指定的值
}
// set 访问器
set
{
// 设置 index 指定的值
}
}
public class Teacher
{
private List<Teacher> teachers = new List<Teacher>();
//索引器
public Teacher this[int index]
{
get
{
if(index>=0 && index<teachers.Count)
return teachers[index];
return null;
}
set
{
if (index >= 0 && index < teachers.Count)
teachers[index]=value;
}
}
}
1.12.2. 索引器重载
索引器也可以被重载,而且在声明索引器时也可以带有多个参数,每个参数可以是不同的类型。另外,索引器中的索引不是必须是整数,也可以是字符串类型。
public class Teacher
{
public string TeacherName { get; set; }
private List<Teacher> teachers = new List<Teacher>();
//索引器
public Teacher this[string name]
{
get
{
for(int i=0;i<teachers.Count;i++)
{
if(teachers[i].TeacherName==name)
return teachers[index];
}
return null;
}
set
{
for(int i=0;i<teachers.Count;i++)
{
if(teachers[i].TeacherName==name)
{
teachers[i]=value;
break;
}
}
}
}
}
二. 继承
继承是面向对象编程的三大特性之一。继承就是基于一个现有的类来定义一个新类型,通过继承新定义的类可重用、扩展和修改被继承类中定义的成员。被继承的类称为“基类(父类)”,继承基类的类称为“派生类(子类)”。
2.1. 定义派生类
当基于一个现有类,定义一个新类,定义语法:
class 派生类:基类
{
}
//基类
public class People
{
public int Id{get;set;}
public string Name{get;set;}
}
//派生类
public class SchoolPeople:People
{
public string No{get;set;}
public string Position{get;set;}
}
需要注意的是:C# 中只支持单继承,也就是说一个派生类只能继承一个基类,但是继承是可以传递的,如 类型B 继承了 类型A,而 类型C继承了 类型B,那么 类型C 将继承 类型B 和 类型A 中的所有成员。
public class Teacher:SchoolPeople
{
public string TeacherNo{get;set;}
}
这里Teacher类继承SchoolPeople类,SchoolPeople类又继承于People类,所以Teacher类拥有SchoolPeople类和People类中所有的属性。但定义时,不写像下面这样写:
//这个写法是错误的,C#只支持单继承
public class Teacher:SchoolPeople,People
{
}
2.2. 多重继承
多重继承是指一个类可以同时继承多个基类,C# 并不支持多重继承,但是可以借助接口来实现多重继承。(接口后面会介绍)
public interface IPeople
{
void Work();
}
// 这里的Teacher继承People,同时实现了接口IPeople
public class Teacher:People,IPeople
{
public void ShowInfo()
{
Console.WriteLine("编号:"+Id+",姓名:"+Name+",老师号:"+TeacherNo);
}
public void Work()
{
Console.WriteLine("老师:"+TeacherNo+","+Name+" 正在上课!");
}
}
三. 多态
多态也是为面向对象编程的三大特性之一。 多态(Polymorphism)是一个希腊词,指“多种形态”,
在 C# 中具有两种类型的多态:
-
编译时多态:通过 C# 中的方法重载和运算符重载来实现编译时多态,也称为静态多态;
-
运行时多态:通过方法重写(虚方法、抽象方法)实现的运行时多态,也称为动态多态。
3.1. 静态多态
在编译期间将方法与对象链接的机制称为早期绑定,也称为静态绑定。C# 提供了方法重载和运算符重载两个方式。
方法重载
在同一个作用域中,可以定义多个同名的方法,但是这些方法彼此之间必须有所差异,比如参数个数不同或参数类型不同等等,返回值类可以不同。
//方法重载
public class CalculateHelper
{
public int Add(int a,int b)
{
return a+b;
}
public double Add(double d1,double d2)
{
return d1+d2;
}
public int Add(int a,int b,int c)
{
return a+b+c;
}
}
//调用
CalculateHelper helper=new CalculateHelper();
int re1=Add(10,24);
int re2=Add(23,10,45);
double re3=Add(4.5,1.9);
上面三个Add方法就是方法重载,调用时,通过传入参数就确定了调用的方法。
运算符重载
C# 中支持运算符重载,运算符重载就是我们可以使用自定义类型来重新定义 C# 中大多数运算符的功能。运算符重载需要通过 operator 关键字后跟运算符的形式来定义的,我们可以将被重新定义的运算符看作是具有特殊名称的函数,与其他函数一样,该函数也有返回值类型和参数列表。如下代码:
public class StringNew
{
public string Str1 { get; set; }
public string Str2 { get; set; }
public void SetStrs(string str1, string str2)
{
Str1 = str1;
Str2 = str2;
}
public static StringNew operator+(StringNew s1 , StringNew s2)
{
StringNew str = new StringNew();
str.Str1=(int.Parse(s1.Str1)+int.Parse(s2.Str1)).ToString();
str.Str2 = (int.Parse(s1.Str2) + int.Parse(s2.Str2)).ToString();
return str;
}
}
//使用:
StringNew str1 = new StringNew();
StringNew str2= new StringNew();
str1.SetStrs("12", "23");
str2.SetStrs("30", "40");
StringNew str3= new StringNew();
str3 = str1 + str2;
Console.WriteLine(str3.Str1+","+str3.Str2); //42,63
这段代码定义了一个新的类型StringNew,对运算符+重载,实现将两个StringNew对象中的数字字符串的数值分别求和,最后得到的是两对象的求和后的两个数值字符串。
3.2. 动态多态
当程序运行时,才能决定执行何种操作,就是运行时多态。通过抽象方法和虚方法实现运行时多态。
虚方法
在基类中,定义一个方法前面用virtual修饰,并可以在子类中用override重写的方法就是虚方法。
特点:
必须用virtual修饰的方法---基类,用override重写,可以在子类重写,也可以不重写,方法体必须有,哪怕空的也行,不能使用sealed修饰,否则不能重写
子类中重写基类中的虚方法,必须保证三相同:方法名称相同、参数列表相同、返回值类型相同
public class People
{
//虚方法
public virtual void Work()
{
Console.WriteLine("正在工作");
}
}
public class Teacher:People
{
//重写虚方法
public override void Work()
{
Console.WriteLine("老师正在工作");
}
}
public class Student:People
{
//重写虚方法
public override void Work()
{
Console.WriteLine("学生正在工作");
}
}
子类Teacher和Student都重写了基类People中的虚方法Work,不同的子类,重写的实现不同,就会表现出不同的行为。
覆写
派生类中可以声明和基类同名方法,虚方法和非虚方法都可以被覆写。在子类中用new关键字修饰。
基类中
public void EatFood()
{
Console.WriteLine( "吃饭");
}
子类中
//new 将非虚方法覆写
public new void EatFood()
{
Console.WriteLine("教师:" + Name + " 正在吃饭!");
}
//调用
teacher.EatFood();//调用子类中的方法
people.EatFood();//执行的People类中的方法
C# 允许您使用 abstract 关键字来创建抽象类(前面介绍过)。抽象类包含抽象方法,可以在派生类中实现。
抽象方法
C# 允许使用关键字 abstract 修饰的方法——抽象方法。
特点:
-
抽象只能在抽象类中定义,在抽象类的派生类中实现。
-
抽象方法实现用override,是隐式的虚方法
-
实现抽象方法的方法不能是抽象成员。
public abstract class AbPeople
{
//抽象方法
public abstract void Work();
}
public class AbTeacher:AbPeople
{
public string TeacherName{get;set;}
//实现抽象方法
public override void Work()
{
Console.WriteLine($"老师:{TeacherName} 正在工作");
}
}
AbTeacher teacher=new AbTeacher();
teacher.Work();
抽象方法与虚方法区别 | |
---|---|
关键字不同 | 抽象方法 :abstract 虚方法:virtual |
定义位置要求 | 抽象方法必须在抽象类中,虚方法不需要(抽象类或普通类均可) |
默认实现 | 抽象方法不能有实现,虚方法必须实现 |
是否必须实现 | 抽象方法子类必须实现,虚方法子类可以不实现 |
3.19 接口
接口(Interface)用来定义一种程序的语法合同,可以看作是一个约定,其中定义了类或结构体继承接口后需要实现的功能。
接口定义了“做什么”,不管“怎么做”。
接口的特点:
-
接口是一个引用类型,通过接口可以实现多重继承;
-
接口中只能声明"抽象"成员,所以不能直接实例化接口;
-
接口中可以包含方法、属性、事件、索引器等成员,但主要定义方法,但不提供成员的实现;
-
接口名称一般使用字母“I”作为开头(不是必须的,但最好这样定义);
-
接口中成员的访问权限默认为 public,因此在定义接口时不指定接口成员的访问修饰符,否则编译器会报错;
-
不能为接口成员编写具体的实现代码,只要在定义成员时指明成员方法的名称、参数列表、返回值类型就可以了;
-
接口一旦被实现,派生类就必须实现接口中的所有成员。
声明接口
C# 中声明接口需要使用 interface 关键字,语法如下:
public interface 接口名{
返回值类型 方法名1(参数列表...);
返回值类型 方法名2(参数列表...);
... ...
}
其中,接口名称以I开头命名。
接口实现:一个类如果实现接口,需要实现接口中的方法,方法名必须与接口中定义的方法名一致。
//接口
public interface IUser
{
bool Login(string userName,string userPwd);
void AddUser(UserInfo user);
}
//实现接口
public Class UserService:IUser
{
List<UserInfo> userList=new List<UserInfo>();
public bool Login(string userName,string userPwd)
{
if(userName=="admin" && userPwd=="123456")
return true;
else
return false;
}
public void AddUser(UserInfo user)
{
userList.Add(user);
}
}
//调用接口
IUser userService=new UserService();
bool blLogin=userService.Login("lycchun","123456");
userService.AddUser(new UserInfo(){UserId=101,UserName="lycchun",UserPwd="123456"});
接口继承
在 C# 中,一个接口可以继承另一个接口。如果一个接口继承其他接口,那么实现类或结构就需要实现所有接口的成员。
如:接口 1 继承接口 2,一个类来实现接口 1 时,必须同时实现接口 1 和接口 2 中的所有成员。
//接口1
public interface IPeople
{
void Work();
}
//接口2,继承于接口1
public interface ITeacher:IPeople
{
void ShowInfo(TeacherInfo teacher);
}
//实现接口2
public class TeacherService:ITeacher
{
public void Work()
{
Console.WriteLine("老师正在上课!");
}
public void ShowInfo(TeacherInfo teacher)
{
Console.WriteLine("Id:"+teacher.TeacherId+", 姓名:"+teacher.TeacherName);
}
}
//调用
ITeacher teacherService=new TeacherService();
teacherService.Work();
TeacherInfo teacher=new TeacherInfo(){
TeacherId=111,
TeacherName="李老师"
};
teacherService.ShowInfo(teacher);
接口 与抽象类对比
相同点:
-
都可以被继承(实现)
-
都不能被实例化
-
都可以包含方法声明
-
派生类必须实现定义的方法
区别:
-
抽象类可以定义字段、属性、方法实现。接口只能定义属性、索引器、事件、和方法声明,不能包含字段。(一般通常只定义方法)
-
抽象类是一个不完整的类,需要进一步细化;而接口是一个行为规范,规定能“做什么”。
-
接口可以被多重实现,抽象类只能被单一继承
-
抽象类更多的是对一系列紧密相关的类的抽象出来的概念,强调“是什么”;而接口大多数是对众多类型的相同功能的规定,重在行为定义(功能),强调“做什么”,而不关心怎么做。
两者选择:
-
如果是关系密切的对象,特征和行为都包含,选择抽象类
-
如果只是注重行为的抽象,选择接口
3.20 命名空间
在 C# 中,可以将命名空间看作是一个范围,用来标注命名空间中成员的归属,一个命名空间中类与另一个命名空间中同名的类互不冲突,但在同一个命名空间中类的名称必须唯一。
如果要访问某个命名空间中的类,我们需要使用 命名空间名.类名 的形式。当然也可以使用 using 关键字来引用需要的命名空间,然后就直接使用 类名.方法名()的形式。
使用命名空间的好处:可以避免命名冲突,同时也便于管理类。
定义命名空间
定义命名空间需要使用 namespace 关键字,语法格式如下:
namespace 命名空间名
{
// 命名空间中的代码 类及类中的成员
}
有的情况下,不同命名空间下有相同的类名,要调用指定命名空间下的类,则需要使用`命名空间.类名 的形式。
namespace MySpace1
{
public class UserInfo
{
public void Show()
{
Console.WriteLine("编号:01,姓名:李明");
}
}
}
namespace MySpace2
{
public class UserInfo
{
public void Show()
{
Console.WriteLine("编号:02,姓名:王林");
}
}
}
namespace Zhaoxi.ConsoleApp.ClassCourse
{
class Program
{
static Main(string[] args)
{
MySpace1.UserInfo user01=new MySpace1.UserInfo();
user01.Show();
MySpace2.UserInfo user02=new MySpace2.UserInfo();
user02.Show();
}
}
}
引用命名空间
用using 关键字来引用指定的命名空间,它可以告诉编译器后面的代码中我们需要用到某个命名空间。
如我们在程序中需要使用到 System 命名空间,只需要在程序的开始使用using System
引用该命名空间即可。后面在代码中我们如果用到System命名空间下的类,不需要在类名前加命名空间名了。
using System;
using MySpace2;
namespace Zhaoxi.ConsoleApp.ClassCourse
{
class Program
{
static Main(string[] args)
{
MySpace1.UserInfo user01=new MySpace1.UserInfo();
user01.Show();
UserInfo user02=new UserInfo();
user02.Show();
}
}
}
3.21 异常处理
在 C# 中,异常是在程序运行时出现的错误,如将一个空字符串转换为值类型时,就会引发异常。
所有异常都派生自 System.Exception 类。
异常处理就是:处理运行时错误的过程,使用异常处理可以使程序在发生错误时保持正常运行,而不中断运行。
异常提供了一种把程序控制权从某个部分转移到另一个部分的方式。
C# 中的异常处理基于四个关键字构建,分别是 try、catch、finally 和 throw。
-
try:try 语句块中通常用来放容易出现异常的代码,其后面紧跟一个或多个 catch 语句块;
-
catch:catch 语句块用来捕获 try 语句块中的出现的异常;
-
finally:finally 语句块用于执行特定的语句,不管异常是否被抛出都会执行;
-
throw:throw 用来抛出一个异常。
try catch语句
try catch 组合语句用来捕获程序异常,语法格式:
try
{
// 可能会引起异常的语句块
}
catch( ExceptionName e1 ) //异常类型1
{
// 错误处理代码
}
catch( ExceptionName e2 ) //异常类型2
{
throw e2; //抛出异常
}
。。。
finally //可以没有finally语句块
{
// 不管是否有异常,都要执行的语句
}
int val;
try
{
string str="";
val=int.Parse(str);
}
catch (Exception e)
{
Console.WriteLine($"异常消息:{e.Message}");
}
finally
{
val=-1;
}
C#异常类
C# 中的异常有多种类型,异常类主要是直接或间接地派生于 从 System.Exception 类派生的,比如 System.ApplicationException 和 System.SystemException 两个异常类就是从 System.Exception 类派生的。
-
System.ApplicationException 类支持由应用程序产生的异常,因此我们自定义的异常都应继承此类;
-
System.SystemException 类是所有系统预定义异常的基类。
下表中列举了一些从 Sytem.SystemException 类派生的预定义异常类:
异常类 | 描述 |
---|---|
System.IO.IOException | 处理 I/O 错误 |
System.IndexOutOfRangeException | 处理当方法引用超出范围的数组索引时产生的错误 |
System.ArrayTypeMismatchException | 处理当数组类型不匹配时产生的错误 |
System.NullReferenceException | 处理引用一个空对象时产生的错误 |
System.DivideByZeroException | 处理当除以零时产生的错误 |
System.InvalidCastException | 处理在类型转换期间产生的错误 |
System.OutOfMemoryException | 处理空闲内存不足产生的错误 |
System.StackOverflowException | 处理栈溢出产生的错误 |
int val;
int a = 12;
int b = 0;
try
{
string str="";
val=int.Parse(str);
int count=a/b;
}
catch (FormatException e)
{
Console.WriteLine($"异常消息:{e.Message}");
}
catch (DivideByZeroException e)
{
Console.WriteLine($"异常消息:{e.Message}");
}
如果存在方法调用,在调用的方法内部进行了异常捕获,调用处即使异常捕获也是捕获不到的。因此,异常捕获在调用层面进行,调用方法的内部一般不进行异常捕获,即便不得不进行异常捕获,也不能把异常给处理了,将异常向上抛,这样上方才能捕获到。