
刘铁锰老师C#语言入门详解(委托事件等部分有详细代码和注释)
刘铁锰老师C#语言入门详解,内含委托事件等详细代码和注释
目录
1、初识类
小白:类是构成程序的主体,类是对现实世界事物抽象得到的结果。
资深:类 (class) 是最基础的 C# 类型。类是一个数据结构,将状态(字段)和操作(方法和其他函数成员)组合在一个单元中。类为动态创建的类实例 提供了定义,实例也称为对象 。类支持继承 和多态性 ,这是派生类 可用来扩展和专用化基类的机制。
《C#语言规范》:类是一种数据结构,它可以包含数据成员(常量和字段)、函数成员(方法、属性、事件、索引器、运算符、实例构造函数、静态构造函数和析构函数)以及嵌套类型。类类型支持继承,继承是一种机制,它使派生类可以对基类进行扩展和专用化。
刘铁锰老师:
1、类是一种抽象数据结构。为什么是抽象?首先,类本身是抽象的结果,把学生对象抽取iD姓名等信息封装起来就是一个学生类;其次类是抽象结果(数据或者行为)的载体,把抽象出来的学生的ID等数据方法封装起来这个过程;类承载了所有和抽象相关的编程方法,比如说继承多态。
2、类是一种数据类型,一种引用类型,每一个类都是自定义引用类型。可以声明变量,可以创建实例,视为实例的模板。
3、代表现实世界中的“种类”,凸显出静态成员。内存泄漏:可以动态使用的内存越来越少;
栈溢出:内存栈区的使用空间被用光。构造器(构造函数):初始化器,默认的没有输入参数,可以通过重载添加;
析构器(析构函数):快捷键ctor,当对象消失前执行析构器。命名空间以树型结构组织类(和其他类型),不同名称空间会有相同的类,使用“命名空间+.”。
1.1 类与对象的关系
- 对象也叫实例,是类经过实例化后得到的内存中的实体
- 依照类,我们可创建对象,就是实例化
- 使用new操作符创建类的实例
- 引用变量与实例的关系
1.2 类的三大成员
- 属性:存储数据,组合起来表示类和对象当前的状态,可以通过变量存放数据;
- 方法:由函数进化而来,表示类和对象能做什么,构成逻辑(算法)的成员;
程序是数据加算法,无论编程怎么改变这两个点都不会变。- 事件:类和对象通知其他类和对象的机制;
- 使用MSDN文档:将鼠标放在上面,点击F1,就可以查阅MSDN文档;
- 某些特殊类或者对象在成员方面侧重点不同。
1.3 静态成员与实例成员
静态(static)成员在语义上表示它是类的成员;
实例(非静态)成员在语义上表示它是对象的成员;
绑定(Binding)指的是编译器把一个成员与类或者对象关联起来;通过+.+实现。
1.4 类修饰符
public 公有访问,不受任何限制;
private 私有访问,只限于本类成员访问(子类、父类、实例都不能访问);
protected 保护访问,只限于本类、子类、父类访问(实例不能访问);
internal 内部访问,只限于本命名空间(程序集)内访问,其他命名空间(程序集)内不能访问,程序集就是命名空间;
protected internal 保护访问 和 内部访问的并集,符合任意一条都可以访问。
2、构成C#语言的基本元素
2.1 六个基本元素
- 关键字(Keywords)(保留字):构成一门语言的基本词汇 。
什么时候都是关键字:
上下文关键字,在某些上下文环境是关键字,出了就不是了:
- 操作符(Operator)(运算符):用来表达运算思想的符号。
- 标识符(Identifier):起名字。不能是保留字,字母、数字、下划线组成,但是不能用数字开头,这种覆盖百分之90情况,还有具体的去看定义文档。
对类的属性命名使用名词,对方法命名使用动词+名词 ;大小写规范,驼峰法,帕斯卡法。
- 标点符号
- 文本(字面值):整数、实数、字符、字符串、布尔、空(null)
- 注释与空白:// /* */
前5个统称为标记(Token),即对编译器有用的东西.
2.2 算法简介
循环、递归。想要学算法刷力扣去,给个学习的链接,是一个大佬整理的:
3、 详解类型、变量与对象
3.1 类型概述
又叫数据类型:
- 是数据在内存中存储的型号
冯诺依曼中运算器+控制器就是我们计算机中的CPU,存储器就对应着内存- 小内存容纳大尺寸数据会丢失精度,发生错误
- 大内存容纳小尺寸数据会导致浪费
- 编程语言的数据类型与数学中的数据类型不完全相同
C#是强类型语言,类型包含的信息有:
- 存储此类型变量所需的内存空间大小
- 此类型的值可表示的最大、最小范围
- 此类型所包含的成员(方法、属性、事件等)
- 此类型由何基类派生而来
- 程序运行的时候,此类型的变量在分配在内存的什么位置
静态时期,还没有运行,还在写代码的时候
动态时期,一旦运行,调试中
反射(后面说)
栈(stack):比较小,快,方法调用,会出现爆了的情况(算法写的不好函数调用过多,程序错误栈溢出)从高地址开始分配
堆(heap):比较大,存储对象(实例 )(内存泄漏,自带的垃圾处理)
查看进程的堆使用情况:win+R打开运行,输入perfmon回车- 此类型所允许的操作运算
3.2 C#五大的数据类型
- 类(class):按F12,查看
- 结构体(struct)
- 枚举(enum):限定用户选择一些值
C#数据类型包括引用类型和值类型两种,引用类型又包括类、接口和委托,而值类型包括结构体和枚举,所有类型都以Object为自己的基类型。
3.3 变量、对象与内存
- 什么是变量
表面上就是存储数据;
实际上,变量表示了存储位置,并且每个变量都有一个类型,以决定什么值可以存入变量;
变量有七种:静态变量、实例变量(成员变量、字段)、数组元素、值参数、引用参数、输出参数、局部变量;
狭义的变量就是局部变量,简单地说局部变量就是方法体(函数体)里声明的变量;
变量的声明:有效的修饰符组合(opt)类型 变量名 初始化(opt)。
变量,以变量名所对应的内存地址为起点、以其数据类型所要求的存储空间位长度的一块内存区域值类型的变量
值类型没有实例,实例与变量合而为一,直接分配地址- 引用类型的变量
先分配四个字节的内存用来存实例的地址,再在堆给引用的类型分配空间- 局部变量是在栈上分配内存
- 变量的默认值,默认为
- 常量(const),值不可改变的值
- 装箱与拆箱
装箱:把值类型转化成引用类型,分配四个字节的内存,把值类型的地址保存在该内存。所要引用的值如果不是堆上的实例,而是栈上的值类型值,先把栈上值类型值copy一下然后在堆上找一块可以存储的地址空间,把这个地址空间变成一个对象,这个地址空间的地址存储在四个字节的内存中。
拆箱:把引用类型转化成值类型,从引用地址找到堆上的对象,将堆上的值复制到栈上。
4、方法
4.1 方法的定义
- 方法的前身是C/C++语言中的函数
方法是面向对象范畴的概念,在非面向对象语言中仍然称为函数- 永远都是类(或结构体)的成员
C#中函数不可能独立于类(或者结构体)之外
只有作为类(结构体)的成员才被称为方法
c++是可以的,被称为全局函数- 是类(结构体)最基本成员之一
最基本的成员有2个,方法和字段(成员函数与成员函数),本质还是数据+算法
方法表示类(结构体)能做什么- 为什么需要方法和函数
目的1:隐藏复杂的逻辑
目的2:将大算法分解成小算法
目的3:复用(reuse,重用)- 声明方法的语法详解
参见C#语言文档
Parameter是一种变量,形式上的参数,简称形参- 方法的命名规范
大小写规范、需要以动词或者动词短语作为名字- 调用方法
调用方法时argument列表要与定义方法是的parameter列表相匹配
4.2 方法的调用和调试
- 构造器是类型成员之一
- 狭义的构造器指的是“实例构造器”
- 如何调用构造器
- 声明构造器
快捷键:ctor+两次tab- 构造器的内存原理
局部变量保存在栈里,首先从高地址开始分配,在堆里找可以分配的大小的空间,如果其中有引用类型,还需要再次分配,就涉及到多次内存转储。- 方法的重载
方法签名由方法的名称、类型形参的个数和它的每一个形参(从左到右的顺序)的类型和种类(ref out)(值、引用或输出)组成。方法签名不包含返回值
重载决策:用给定的传送的参数的类型来确定哪个方法- 对方法进行debug
设置断点
观察方法调用时的call stack
step-in (F11 一步步) step-over(F10 直接跳到) step-out(shift+F11 直接跳出当前方法)
观察局部变量的值与变化- 方法的调用与栈
主调用者在调用方法的时候会传入n个变量,这些变量归主调用者管,并且先压左边后压右边的变量(谁调用谁负责把变量压入栈)
其中返回值一般都是保存在CPU的寄存器中,如果太多会在栈上开辟空间保存。
5、操作符
5.1 操作符的本质
- 操作符又叫运算符
- 操作符的本质是函数(算法)的简记法。
- 操作度不能脱离与它相关联的数据类型。
5.2 操作符的优先级
- 可以使用圆括号提高被括起来的表达式的优先级。
- 圆括号可以嵌套,且只有圆括号。
- 上图从上到下优先级以此最高,同一级别除赋值运算符以外从左到右依次运行,赋值运算符从右往左运算。
- 与数学运算不同,计算机语言同优先级运算没有结合律。
5.3 部分操作符
- typeof查看一个类型的内部结构,比如命名空间、数据类型、哪些方法。(GetType())
- default获取一个类型的默认值。
- var:定义一个变量,类型不知道计算机自己推断,C#是强类型,一旦确定不可更改。
var person =new{Name = "Mr.ok",Age = 25};
- new一旦使用,会使两个类产生耦合,一旦依赖的类不能使用,使用的类也运行失败;
new可以隐藏父类方法。- C#默认采用unchecked模式,溢出不异常,checked溢出会有异常。
- sizeof只能用于获取基本数据类型(结构体类型)在内存中占有多少字节,unsafe可以获取自定义结构体类型在内存中占有多少个字节。
- 一元操作符,只需要一个操作数。
- ~按位取反(二进制)。
- <<(二进制)左移一位,相当于*2(无溢出),不管正数负数补位都是0
>>(二进制)右移,相当/2(无溢出),正数补0负数补1.- 类型监测is,检验一个实例对象是不是某个类型(派生)的对象,结果是bool类型
as,如果是返回该对象,否则返回null。- 逻辑与&,按位与,二进制中都为1才是1,其他都是0
逻辑或|,按位或,二进制中都为0才是0,其他都是1
异或^,二进制两位不一样为1,一样为0- 条件AND(&&),条件OR(||),用来操作bool类型,
短路效应,条件与左边不成立右边直接跳过,条件或左边成立右边直接跳过。- int? x =null;可空
观察x是否为空,可以用 int y = x??0;如果x为空就给y一个0。- null合并运算符,返回类型是??左边的类型
- 条件运算符:返回类型是冒号左右精度更加高的一个
int a=10; string str =(a>20)?"Yes":"No";
- 赋值表达式也有自己的数据类型,赋值表达式的值是左边的值,数据类型是左边变量的数据类型。
5.4 类型转换
- 隐式类型转换
不丢失进度的转换(部分):
子类向父类的转换:多态
装箱- 显示类型装换(部分):
有可能丢失精度(甚至发生错误)的准喊,即cast:(double)x
拆箱
使用Convert类:Convert.Todouble(x)
Tostring方法与各数据类型的Parse/TryParse方法:x.Tostring(),double.Parse()(正确的)。- 自定义类型转换操作符
6、表达式、语句
6.1 表达式的定义
- 所有语言:表达式是基本组件之一,而且是基本组件最重要的一个。表达式是一种语法实体,专门用来求值的,如果失败了就会有异常。
- C#语言:表达式是由一个或者多个操作数和零个或者多个运算符组成的序列,可以计算为单个值、对象、方法或者命名空间。表达式可以由文字、方法调用、运算符、操作符或者简单名称组成。简单名称可以是变量、类型成员、方法参数、命名空间或类型的名称。
算法逻辑的最小单元,表达一定的算法意图。
因为操作符有优先级,所以表达式就有了优先级。
6.2 语句的定义
- 语句的定义:
在计算机编程中,语句是命令式编程语言中最小的独立元素,它表示要执行的某些操作。用这种语言编写的程序由一个或者多个语句的序列构成。语句将包含内部组件(例如表达式)。
语句是高级语言的语法-编译语言和机器语言只有指令(高级语言中的表达式对应低级语言中的指令),语句等价于一个或一组有明显逻辑关联的指令。举例:求圆柱体积。- 如何反编译打开exe文件,以win10/vs2019为例:
直接搜 Developer Command Prompt:
输入ildasm.exe回车:
右击文件,点击打开选择需要导入的exe文件:
打开后就可以观察反编译后的类、方法、参数、返回值等信息,并可以双击查看其对应编译好的二进制代码:- C#中对语句的定义:
C#程序采取以语句的形式表示。常见的操作包括声明变量、赋值、调用方法、循环集合以及根据给定条件分支到一个或另一个代码块。在程序中执行语句的顺序称为控制流或执行流。每次运行程序时,控制流程可能会有所不同,这取决于陈旭对运行时接收到的输入;
C#语言的语句除了能够让程序员顺序地表达算法思想,还能通过条件判断和循环等方法控制 程序逻辑的走向;
简而言之就是:陈述算法思想,控制逻辑走向,完成有意义的动作;
C#语言的语句是由分号结尾,但由分号结尾的不一定是语句
语句一定是出现在方法体里。
6.3 部分类型的语句
C#有三类语句:标签语句、声明语句、嵌入语句。
1、声明语句(分为局部变量声明和局部常量声明)
int x =100;//声明1 int x;//声明2 x =100;
声明1声明变量的时候追加了变量的初始化器,只有一步操作,声明2声明变量的时候没有初始化,后面对变量赋值,有两步操作。常量(const)必须用声明1
2、表达式语句(嵌入式语句一种)
用于计算所给的的表达式,由此表达式计算出的值会被抛弃。
3、块语句(嵌入式语句一种)
一对花括号里面的语句,块语句会被编译器当成一条语句,编译器会认为块语句是一条完整的语句,不用在最后加分号。
块语句内声明的变量出了快模块就访问不了。4、if语句(选择语句,嵌入式语句一种)
if只算一条语句,只有if后的为真才会执行后面的嵌入式语句。
if语句快捷键:if+两次tab。5、Switch语句(选择语句,嵌入式语句一种)
switch 语句选择一个要执行的语句列表,此列表具有一个相关联的 switch 标签,它对应于 switch 表达式的值。
- 如果 switch 表达式的类型为 sbyte、byte、short、ushort、int、uint、long、ulong、bool、char、string 或 enum-type,或者是对应于以上某种类型的可以为 null 的类型,则该类型就是 switch 语句的主导类型。(没有浮点类型)
- 否则,必须有且只有一个用户定义的从 switch 表达式的类型到下列某个可能的主导类型的隐式转换(第 6.4 节):sbyte、byte、short、ushort、int、uint、long、ulong、char、string 或对应于以上某种类型的可以为 null 的类型。
- 否则,如果不存在这样的隐式转换,或者存在多个这样的隐式转换,则会发生编译时错误。
C#中每个语句后面必须加一个break,可以多个标签对应一个语句。
Switch快捷键:sw+两次tab。6、try语句
try 语句提供一种机制,用于捕捉在块的执行期间发生的各种异常。此外,try 语句还能让您指定一个代码块,并保证当控制离开 try 语句时,总是先执行该代码。
catch语句可以有多个,throw 语句将引发一个异常,直接交给调用者来处理,如果throw后面没有语句,就是直接抛弃异常。7、while、do while、for、foreach(循环语句)
while 可能0次或者多次,括号里是块语句是一条语句。
do while 至少运行一次。
for 语句计算一个初始化表达式序列,然后,当某个条件为真时,重复执行相关的嵌入语句并计算一个迭代表达式序列。(快捷键:for+2下tab)
for中圆括号有三个部分,第一个是初始化器,第二个是条件(为真),第三个循环一次运行一次。运行顺序:首先第一部分只执行一次,然后第二部分确定是否运行循环体,运行循环体后运行第三部分,再到第二部分。
foreach 语句用于枚举一个集合的元素,并对该集合中的每个元素执行一次相关的嵌入语句。遍历这个集合。
可以被遍历的类型:所有数组类型基类都是array,所有可以被迭代的集合都有自己的迭代器(IEnumerator),迭代器有个MoveNext的方法,可以一直循环迭代所有元素,reset可以重置。foreach遍历就是将集合遍历中迭代变量从头到尾依次遍历一遍。(快捷键:foreach+2下tab)8、break 、continue、goto、return、throw(跳转语句)
break 语句将退出直接封闭它的 switch、while、do、for 或 foreach 语句(当前)。当多个 switch、while、do、for 或 foreach 语句互相嵌套时,break 语句将只应用于最里层的那个语句。若要穿越多个嵌套层转移控制,必须使用 goto 语句。
continue 语句将开始直接封闭它的 while、do、for 或 foreach 语句的一次新迭代(只能在这是个里面)。continue 语句不能退出 finally 块。当 continue 语句出现在 finally 块中时,该 continue 语句的目标必须位于同一个 finally 块中,否则将发生编译时错误。
return,尽早return。不是void,每个方法都需要return。
throw 语句将引发一个异常。带表达式的 throw 语句引发一个异常,此异常的值就是通过计算该表达式而产生的值。不带表达式的 throw 语句只能用在 catch 块中,在这种情况下,该语句重新引发当前正由该 catch 块处理的那个异常。
7、字段、属性、索引器、常量(表达数据)
7.1 字段
字段 (field) 是一种表示与对象或类关联的变量的成员。field-declaration 用于引入一个或多个给定类型的字段。
1、什么是字段
- 字段 ( field ) 是一种表示与对象或类型 (类与结构体 ) 关联的变量字段是类型的成员,旧称“成员变量”
- 与对象关联的字段亦称实例字段
- 与类型关联的字段称为”静态字段”,由static修饰
2、字段的声明
一定要写在类体里,不是语句,语句写在函数体内。
- 参见C#语言定义文档
- 尽管字段声明带有分号但它不是语句
- 字段的名字一定是名词
3、字段的初始值
- 无显式初始化时,字段获得其类型的默认值,所以字段永远都不会未被初始化
- 实例字段初始化的时机-对象创建时
- 静态字段初始化的时机-类型被加载( load ) 时,静态永远只执行一次
4、只读字段
实例只读字段、静态只读字段
7.2 属性
1、什么是属性
属性( property )是一种用于访问对象或类型的特征的成员,特征反映了状态
属性是字段的自然扩展
- 从命名上看,field更偏向于实例对象在内存中的布局, property更偏向于反映现实世界对象的特征
- 对外:暴露数据,数据可以是存储在字段里的,也可以是动态计算出来的
- 对内:保护字段不被非法值"污染"
属性由Get/Set方法对进化而来
又一个"语法糖"一一属性背后的秘密2、属性的声明(propfull+2下tab)
- 完整声明一一后台 ( back )成员变量与访问器(注意使用code snippet和refactor工具)
- 简略声明一一只有访问器 (查看IL代码)
- 动态计算值的属性
- 注意实例属性和静态属性
- 属性的名字一定是名词
- 只读属性一一只有getter没有setter
尽管语法上正确,几乎没有人使用“只写属性",因为属性的主要目的是通过向外暴露数据而表示对象/类型的状态3、属性与字段的关系
- 一般情况下,它们都用于表示实体(对象或类型)的状态
- 属性大多数情况下是字段的包装器( wrapper )
- 建议:永远使用属性(而不是字段)来暴露数据,即字段永远都是private或protected的
7.3 索引器、常量
1、 索引器:使对象能够与数组相同的方式(下标)进行索引
2、常量是什么(constant)
- 常量(constant)是表示常量值(即,可以在编译时计算的值)的类成员
- 常量隶属于类型而不是对象,即没有实例常量”,类型+.
"实例常量的角色由只读实例字段来担当- 注意区分成员常量与局部常量
3、各种"只读'的应用场景
- 为了提高程序可读性和执行效率一一常量
- 为了防止对象的值被改变一一只读字段
- 向外暴露不允许修改的数据一一只读属性 (静态或非静态),功能与常量有一些重叠
- 当希望成为常量的值其类型不能被常量声明接受时(类/自定义结构体)一一静态只读字段
8、参数的进一步学习

有四种形参:
- 值形参,声明时不带任何修饰符。
- 引用形参,用 ref 修饰符声明。
- 输出形参,用 out 修饰符声明。
- 形参数组,用 params 修饰符声明。
8.1 值形参、引用形参
值类型指类型本身包含其值,值参数指把值复制给形参
1、值参数(参数的种类)->值类型(数据类型)
值参数,声明时不带修饰符的形参是值形参。一个值形参对应于一个局部变量,只是它的初始值来自该方法调用所提供的相应实参。
当形参是值形参时,方法调用中的对应实参必须是表达式,并且它的类型可以隐式转换为形参的类型。
允许方法将新值赋给值参数。这样的赋值只影响由该值形参表示的局部存储位置,而不会影响在方法调用时由调用方给出的实参。
值类型创建变量的副本
对值参数的操作不影响变量的值
2、值参数(参数的种类)->引用类型(数据类型),并且创建新的对象
给引用类型赋值,就是给他一个新对象,就是给了一个新的地址,所以本身改变外面变量不会变化
3、值参数(参数的种类)->引用类型(数据类型),只操作对象,不创建新对象
方法主要输出是依靠返回值,我们将修改参数引用的对象的值叫方法的副作用
4、引用参数
引用形参是用 ref 修饰符声明的形参。与值形参不同,引用形参并不创建新的存储位置。相反,引用形参表示的存储位置恰是在方法调用中作为实参给出的那个变量所表示的存储位置。
变量在可以作为引用形参传递之前,必须先明确赋值。
5、引用参数(参数的种类)->值类型(数据类型)
5、引用参数(参数的种类)->引用类型(数据类型),创建新对象
注:有点像指针的指针
8.2 输出形参,数组参数,具名参数,可选参数,扩展方法
1、输出形参的定义
用 out 修饰符声明的形参是输出形参。类似于引用形参,输出形参不创建新的存储位置。相反,输出形参表示的存储位置恰是在该方法调用中作为实参给出的那个变量所表示的存储位置。
当形参为输出形参时,方法调用中的相应实参必须由关键字 out 并后接一个与形参类型相同的 variable-reference组成。变量在可以作为输出形参传递之前不一定需要明确赋值,但是在将变量作为输出形参传递的调用之后,该变量被认为是明确赋值的。
在方法内部,与局部变量相同,输出形参最初被认为是未赋值的,因而必须在使用它的值之前明确赋值。
在方法返回之前,该方法的每个输出形参都必须明确赋值。
2、值类型
3、引用类型
4、数组参数params
可以不输入数组,只输入一串数字或者其它任意个数类型的参数。
必须是形参列表的最后一个,由params修饰,例:String.Split。
5、具名参数
优点:提高可读性;加上名字后,参数位置可以不受参数列表位置顺序限制
6、可选参数
参数因为带有默认值,可选可不选,不建议使用。
7、扩展方法(this参数)
方法必需是公有的、静态的,即被public static修饰
必需是形参列表的第一个,由this修饰
必需由一个静态类来统一收纳该类型的扩展方法
举例:LINQ方法
9、 委托

9.1委托的简单使用(Action、Func)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Delegate1
{
class Program
{
static void Main(string[] args)
{
Calculator calculator = new Calculator();//实例
//action委托
Action action = new Action(calculator.Report);//能指向什么目标方法,里面方法不加圆括号,加圆括号是调用,不加是只需要方法名
calculator.Report();//直接调用
action.Invoke();//间接调用
action();//间接调用简便方式
//func委托
Func<int, int, int> func1 = new Func<int, int, int>(calculator.Add);
Func<int, int, int> func2 = new Func<int, int, int>(calculator.Sub);
int x = 200;
int y = 100;
int z = 0;
z = func1.Invoke(x, y);
Console.WriteLine(z);
z = func2.Invoke(x, y);
Console.WriteLine(z);
Console.ReadLine();
}
}
class Calculator
{
public void Report()
{
Console.WriteLine("l have 3 methods.");
}
public int Add(int a, int b)
{
int result = a + b;
return result;
}
public int Sub(int a, int b)
{
int result = a - b;
return result;
}
}
}
9.2委托的声明(自定义委托)

注:委托是类,位置应该放在命名空间里,class外面
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Delegate1
{
public delegate double Calc(double x, double y);//委托的自定义,前面的double是返回值类型,后面的是目标参数的参数列表
class Program
{
static void Main(string[] args)
{
Calculator calculator = new Calculator();//实例后才能访问里面的方法
Calc calc1 = new Calc(calculator.Add);//委托的实例化,new后面必须要和声明约束的一样输入参数是两个double参数
Calc calc2 = new Calc(calculator.Sub);//委托的实例化,new后面必须要和声明约束的一样输入参数是两个double参数
Calc calc3 = new Calc(calculator.Mul);//委托的实例化,new后面必须要和声明约束的一样输入参数是两个double参数
Calc calc4 = new Calc(calculator.Div);//委托的实例化,new后面必须要和声明约束的一样输入参数是两个double参数
double a = 100;
double b = 200;
double c = 0;
c = calc1(a, b);
Console.WriteLine(c);
c = calc2(a, b);
Console.WriteLine(c);
c = calc3(a, b);
Console.WriteLine(c);
c = calc4(a, b);
Console.WriteLine(c);
Console.ReadLine();
}
}
class Calculator
{
/// <summary>
/// 参数列表两个double类型参数,返回值都是一个double类型参数
/// </summary>
/// <param name="x">输入参数1</param>
/// <param name="y">输入参数2</param>
/// <returns></returns>
public double Add(double x,double y)
{
return x + y;
}
public double Sub(double x, double y)
{
return x - y;
}
public double Mul(double x, double y)
{
return x * y;
}
public double Div(double x, double y)
{
return x / y;
}
}
}
9.3委托的一般使用(模板方法、回调方法)

目的:最大限度实现代码的重复使用。
模板方法:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Delegate1
{
/// <summary>
/// 模板方法
/// 新建一个产品类,类里面有名称属性 Product
/// 新建一个包装类,表示包装产品,类里面有个产品属性,新建一个包装工厂类新建一个产品类,
/// 类里面有名称属性,类里面有个包装的方法,表示包装产品,因此包装工厂类需要传入一个能够生产产品的方法。
/// 优点:其他类不需要动,只需要不停的修改ProductFactory里的产品
/// </summary>
class Program
{
static void Main(string[] args)
{
//工厂类的实例,有了实例才可以生产或者说调用实现里面的披萨和玩具小汽车
ProductFactory productFactory = new ProductFactory();
//需要包装工厂里面的实例
WrapFactory wrapFactory = new WrapFactory();
//可以开始使用模板方法了,模板方法接收委托变量,准备委托变量
//Func<Product>定义的委托返回值是Product,参数列表为空
//指向的Product MakePizza()返回值是Product,参数列表为空,所以匹配
Func<Product> func1 = new Func<Product>(productFactory.MakePizza);
//指向的Product MakeToyCar()返回值是Product,参数列表为空,所以匹配
Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);
//开始调用模板方法,将func1传入,用一个Box变量接收它的返回值
Box box1 = wrapFactory.WrapProduct(func1);
Box box2 = wrapFactory.WrapProduct(func2);
Console.WriteLine(box1.Product.Name);
Console.WriteLine(box2.Product.Name);
Console.ReadLine();
}
}
class Product//产品
{
public string Name { get; set; }
//每个产品需要有自己的名字
}
class Box//包装箱
{
public Product Product { get; set; }
//每个包装箱都有自己的产品
}
class WrapFactory//把产品包装成盒子交给用户
{
//模板方法,接收一个委托类型的参数,func委托需要返回一个Product类型的对象
public Box WrapProduct(Func<Product> getProduct)
{
Box box = new Box();
Product product = getProduct.Invoke();
//getProduct.Invoke()委托里面的product,可以根据需求修改
//本文中首先传入的是有productFactory.MakePizza方法的委托,所以此时直接跳转到此方法内
//后传入的委托是MakeToyCar,跳转到MakeToyCar内
box.Product = product;
return box;
}
}
class ProductFactory//专门用来生产产品的
{
//Product MakePizza()返回值是Product,参数列表为空
public Product MakePizza()//生产出披萨
{
Product product = new Product();
product.Name = "Pizza";
return product;
}
//Product MakeToyCar()返回值是Product,参数列表为空
public Product MakeToyCar()//生产出玩具小汽车
{
Product product = new Product();
product.Name = "ToyCar";
return product;
}
}
}
回调方法:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Delegate1
{
/// <summary>
/// 模板方法:Product、Box、WrapFactory、ProductFactory
/// 优点:其他类不需要动,只需要不停的修改ProductFactory里的产品
/// 回调方法,在模板方法的基础方法的升级,logger
/// 都是用委托类型的参数,封装一个外部的方法,再传入方法的内部,进行间接调用
/// </summary>
class Program
{
static void Main(string[] args)
{
//工厂类的实例,有了实例才可以生产或者说调用实现里面的披萨和玩具小汽车
ProductFactory productFactory = new ProductFactory();
//需要包装工厂里面的实例
WrapFactory wrapFactory = new WrapFactory();
//可以开始使用模板方法了,模板方法接收委托变量,准备委托变量
//Func<Product>定义的委托返回值是Product,参数列表为空
//指向的Product MakePizza()返回值是Product,参数列表为空,所以匹配
Func<Product> func1 = new Func<Product>(productFactory.MakePizza);
//指向的Product MakeToyCar()返回值是Product,参数列表为空,所以匹配
Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);
//声明logger类实例
Logger logger = new Logger();
Action<Product> log = new Action<Product>(logger.Log);
//开始调用模板方法,将func1传入,用一个Box变量接收它的返回值
Box box1 = wrapFactory.WrapProduct(func1,log);
Box box2 = wrapFactory.WrapProduct(func2,log);
Console.WriteLine(box1.Product.Name);
Console.WriteLine(box2.Product.Name);
Console.ReadLine();
}
}
class Logger//
{
//记录程序的状态,一般没有返回值
public void Log(Product product)
{
Console.WriteLine("Product '{0}' created at {1}.Price is {2}.",product.Name,DateTime.UtcNow,product.Price);
}
}
class Product//产品
{
public string Name { get; set; }
//每个产品需要有自己的名字
public double Price { get; set; }
}
class Box//包装箱
{
public Product Product { get; set; }
//每个包装箱都有自己的产品
}
class WrapFactory//把产品包装成盒子交给用户
{
//模板方法,接收一个委托类型的参数,func委托需要返回一个Product类型的对象
//回调方法,对于没有返回值的委托一般使用action委托
public Box WrapProduct(Func<Product> getProduct,Action<Product> logCallback)
{
Box box = new Box();
Product product = getProduct.Invoke();
//getProduct.Invoke()委托里面的product,可以根据需求修改
//本文中首先传入的是有productFactory.MakePizza方法的委托,所以此时直接跳转到此方法内
//后传入的委托是MakeToyCar,跳转到MakeToyCar内
//回调方法
if (product.Price >= 50)
{
logCallback(product);
}
box.Product = product;
return box;
}
}
class ProductFactory//专门用来生产产品的
{
//Product MakePizza()返回值是Product,参数列表为空
public Product MakePizza()//生产出披萨
{
Product product = new Product();
product.Name = "Pizza";
product.Price = 12;
return product;
}
//Product MakeToyCar()返回值是Product,参数列表为空
public Product MakeToyCar()//生产出玩具小汽车
{
Product product = new Product();
product.Name = "ToyCar";
product.Price = 100;
return product;
}
}
}
9.4委托的高级使用

多播委托:一个委托里封装的不止一个方法
异步=并行,同步=并发
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ConsoleApp3
{
class Program
{
//委托同步异步
static void Main(string[] args)
{
Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.Red };
Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Yellow };
Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Green };
Action action1 = new Action(stu1.DoHomework);
Action action2 = new Action(stu2.DoHomework);
Action action3 = new Action(stu3.DoHomework);
#region 单播多播
单播委托
//action1.Invoke();
//action2.Invoke();
//action3.Invoke();
多播委托
//action1 += action2;
//action1 += action3;
//action1.Invoke();
#endregion
#region 三种同步调用
直接同步调用,使用方法名,在主线程里调用
//stu1.DoHomework();
//stu2.DoHomework();
//stu3.DoHomework();
间接同步调用,使用单播委托进行间接调用
//action1.Invoke();
//action2.Invoke();
//action3.Invoke();
间接同步调用,使用多播委托进行间接调用
//action1 += action2;
//action1 += action3;
//action1.Invoke();
#endregion
#region 异步调用
隐式异步调用,自动生成分支进程,在分支进程中调用封装的方法
两个参数,第一个是回调
//action1.BeginInvoke(null, null);
//action2.BeginInvoke(null, null);
//action3.BeginInvoke(null, null);
显示异步调用,第一种thread
//Thread thread1 = new Thread(new ThreadStart(stu1.DoHomework));
//Thread thread2 = new Thread(new ThreadStart(stu2.DoHomework));
//Thread thread3 = new Thread(new ThreadStart(stu3.DoHomework));
//thread1.Start();
//thread2.Start();
//thread3.Start();
//显示异步调用,第二种Task
Task task1 = new Task(new Action(stu1.DoHomework));
Task task2 = new Task(new Action(stu2.DoHomework));
Task task3 = new Task(new Action(stu3.DoHomework));
task1.Start();
task2.Start();
task3.Start();
#endregion
for (int i = 0; i < 10; i++)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Main thread {0}.",i);
Thread.Sleep(100);
}
Console.Read();
}
}
class Student
{
public int ID{ get; set; }
public ConsoleColor PenColor { get; set; }
public void DoHomework()
{
for (int i = 0; i < 5; i++)
{
Console.ForegroundColor = this.PenColor;
Console.WriteLine($"Student {this.ID} doing homework {i} hour(s)");
//线程休息一秒钟
Thread.Sleep(100);
}
}
}
}
10、 事件
10.1 初步了解事件
事件:使对象或类具有通知能力的成员
事件的功能=通知+可选的时间参数(即详细信息)
10.2 事件的应用
click点击事件过程:用户的操作通过windows操作系统调用按钮的内部逻辑,最终按钮的内部逻辑触发了click事件。
三星案例(windows默认使用的):窗口响应按钮事件,按钮是窗口的一部分。
案例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
namespace CombinationMode1
{
/// <summary>
/// 事件拥有者timer
/// 事件Elapsed
/// 事件的响应者boy,girl
/// 事件处理器Action
/// 事件订阅timer.Elapsed += boy.Action
/// </summary>
class Program
{
static void Main(string[] args)
{
//一个类最重要的三类成员,小扳手是属性,小方块是方法,小闪电是事件
Timer timer = new Timer();
timer.Interval = 1000;
Boy boy = new Boy();
Girl girl = new Girl();
//timer.Elapsed达到事件间隔后发生什么
timer.Elapsed += boy.Action;
timer.Elapsed += girl.Action;
timer.Start();
Console.ReadLine();
}
}
class Boy
{
//internal表示在访问权限是在同一个程序集下
internal void Action(object sender, ElapsedEventArgs e)
{
Console.WriteLine(DateTime.Now);
}
}
class Girl
{
internal void Action(object sender, ElapsedEventArgs e)
{
Console.WriteLine(DateTime.Today);
}
}
}
组合方式:事件拥有者和事件响应者是完全不同的两个对象(1星)
using System;
using System.Windows.Forms;
namespace CombinationMode_1
{
/// <summary>
/// 组合方式:事件拥有者和事件响应者是完全不同的两个对象
/// 事件拥有者form
/// 事件click
/// 事件响应者controller
/// 事件处理器FormClicked
/// 事件订阅this.form.Click += this.FormClicked
/// </summary>
class Program
{
static void Main(string[] args)
{
Form form = new Form();
Controller controller = new Controller(form);
form.ShowDialog();
}
}
class Controller
{
public Form form;
public Controller(Form form)
{
//如果form非空就为Click添加一个处理器
if (form != null)
{
//this表示当前Controller的字段,没有this表示传进来的参数
this.form = form;
this.form.Click += this.FormClicked;
}
}
private void FormClicked(object sender, EventArgs e)
{
//显示当前时间
this.form.Text = DateTime.Now.ToString();
}
}
}
组合方式:事件拥有者和事件响应者是同一个的对象,也就是说一个对象用自己的方法处理订阅自己的事件(2星)。
using System;
using System.Windows.Forms;
namespace CombinationMode_2
{
/// <summary>
/// 组合方式:事件拥有者和事件响应者是同一个的对象
/// 事件的拥有者form,类型是MyForm
/// 事件是click
/// 事件的响应者是form
/// 事件处理器Clicked
/// 事件订阅form.Click += form.Clicked
/// </summary>
class Program
{
static void Main(string[] args)
{
MyForm form = new MyForm();
form.Click += form.Clicked;
form.ShowDialog();
}
}
class MyForm : Form//继承于Form这个类
{
//事件处理器
internal void Clicked(object sender, EventArgs e)
{
this.Text = DateTime.Now.ToString();
}
}
}
组合方式:事件的拥有者是事件的响应者的一个字段成员(3星)
using System;
using System.Windows.Forms;
namespace CombinationMode_3
{
/// <summary>
/// 事件的拥有者是事件的响应者的一个字段成员,
/// 事件的响应者用自己的方法订阅着自己的字段成员的某个事件
/// 事件的拥有者是button,button是form的一个字段成员
/// 事件是button.Click
/// 事件的响应者是myform的对象
/// 事件处理器是ButtonClicked
/// 事件的订阅是this.button.Click += this.ButtonClicked
/// </summary>
class Program
{
static void Main(string[] args)
{
MyForm form = new MyForm();
form.ShowDialog();
}
}
class MyForm : Form
{
private TextBox textBox;
private Button button;
public MyForm()
{
this.textBox = new TextBox();
this.button = new Button();
this.Controls.Add(this.button);
this.Controls.Add(this.textBox);
this.button.Click += this.ButtonClicked;
this.button.Text = "Time";
this.button.Top = 50;
}
private void ButtonClicked(object sender, EventArgs e)
{
this.textBox.Text = DateTime.Now.ToString();
}
}
}
10.3 事件的声明
事件是基于委托的两层意思:
第一层:事件需要委托类型来做一个约束。约束既规定事件能发送什么样的消息给响应者,也规定事件响应者能收到什么样的事件消息。这就决定了事件响应者的事件处理器,必须能够和这个约束匹配上,才能够订阅这个事件。
第二层意思:当事件响应者向事件拥有者提供了能够匹配这个事件的事件处理器之后,需要把事件处理器保存或者记录下来。能够记录或者说引用方法的任务,只有委托类型的实例能够做到。
总结来说,事件这类成员无论从表层约束还是从底层的实现都是依赖委托类型的。委托是底层基础,事件是上层建筑。
事件声明的完整格式:
三个普通类,一个委托类,事件拥有者类,事件处理者类,事件信息类,事件添加器和处理器同属的委托类。
using System;
using System.Threading;
namespace event1
{
/// <summary>
/// 事件完整的声明
/// 点菜的案例:点菜事件是顾客为主体对象
///
/// 事件的拥有者customer
/// 事件customer.Order
/// 事件的响应者waiter
/// 事件处理器waiter.Action
/// 事件订阅customer.Order += waiter.Action;
/// </summary>
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer();
Waiter waiter = new Waiter();
customer.Order += waiter.Action;
//触发事件,一定是事件拥有者的一些内部逻辑触发了事件
customer.Action();
customer.PayTheBill();
}
}
//按照规定,如果class是为了传递事件信息,应该是事件的名字+EventArgs
//如果一个类作为EventArgs使用,它需要派生于EventArgs
public class OrderEventArgs: EventArgs
{
public string DishName { get; set; }
public string Size { get; set; }
}
//按照规定,如果委托是为了声明事件,需要在最后加上EventHander
//使用这个后缀是用来约束事件处理器的
//使用这个后缀是用来表明这个委托是用来存储事件处理器
//委托类型的声明是不能放在类体里的,委托是一个独立的类型
public delegate void OrderEventHander(Customer customer, OrderEventArgs e);
public class Customer
{
//声明一个委托类型的字段,不需要外部访问到
private OrderEventHander orderEventHander;
//声明一个事件,public需要外部访问到,Event是关键字,后面的委托是必要的约束,
//也就是事件基于委托。Order是他的事件名字。
public event OrderEventHander Order
{
//事件处理器的添加器
add
{
this.orderEventHander += value;
}
//事件处理器的移除器
remove
{
this.orderEventHander -= value;
}
}
//付账的钱
public double Bill { get; set; }
public void PayTheBill()
{
Console.WriteLine("l will pay ${0}",this.Bill);
}
//添加一些方法来触发逻辑、事件
public void Walkln()
{
Console.WriteLine("walk into the restaurant.");
}
public void SitDown()
{
Console.WriteLine("Sit down.");
}
public void Think()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Let me think...");
Thread.Sleep(1000);
}
if (this.orderEventHander != null)
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Yu";
e.Size = "large";
//Invoke是间接调用这个委托,this表示当前这个类,
this.orderEventHander.Invoke(this, e);
}
}
public void Action()
{
Console.ReadKey();
this.Walkln();
this.SitDown();
this.Think();
}
}
//事件的响应器
public class Waiter
{
public void Action(Customer customer, OrderEventArgs e)
{
Console.WriteLine("l will serve you the dish-{0}.",e.DishName);
double price = 10;
switch (e.Size)
{
case "small":
price = price * 0.8;
break;
case "large":
price = price * 1.2;
break;
default:
break;
}
customer.Bill += price;
}
}
}
事件声明的简约格式:
using System;
using System.Threading;
namespace event1
{
/// <summary>
/// 事件简化的声明
/// 点菜的案例:点菜事件是顾客为主体对象
///
/// 事件的拥有者customer
/// 事件customer.Order
/// 事件的响应者waiter
/// 事件处理器waiter.Action
/// 事件订阅customer.Order += waiter.Action;
/// </summary>
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer();
Waiter waiter = new Waiter();
customer.Order += waiter.Action;
//触发事件,一定是事件拥有者的一些内部逻辑触发了事件
customer.Action();
customer.PayTheBill();
Console.ReadKey();
}
}
//按照规定,如果class是为了传递事件信息,应该是事件的名字+EventArgs
//如果一个类作为EventArgs使用,它需要派生于EventArgs
public class OrderEventArgs: EventArgs
{
public string DishName { get; set; }
public string Size { get; set; }
}
//按照规定,如果委托是为了声明事件,需要在最后加上EventHander
//使用这个后缀是用来约束事件处理器的
//使用这个后缀是用来表明这个委托是用来存储事件处理器
//委托类型的声明是不能放在类体里的,委托是一个独立的类型
public delegate void OrderEventHander(Customer customer, OrderEventArgs e);
public class Customer
{
//事件简化声明,这里的order是一个事件,事件里包含着一个委托字段
public event OrderEventHander Order;
//付账的钱
public double Bill { get; set; }
public void PayTheBill()
{
Console.WriteLine("l will pay ${0}",this.Bill);
}
//添加一些方法来触发逻辑、事件
public void Walkln()
{
Console.WriteLine("walk into the restaurant.");
}
public void SitDown()
{
Console.WriteLine("Sit down.");
}
public void Think()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Let me think...");
Thread.Sleep(1000);
}
if (this.Order != null)//这里使用事件名来代替前面的字段名,这样用情非得已
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Yu";
e.Size = "large";
//Invoke是间接调用这个委托,this表示当前这个类,
this.Order.Invoke(this, e);
}
}
public void Action()
{
Console.ReadKey();
this.Walkln();
this.SitDown();
this.Think();
}
}
//事件的响应器
public class Waiter
{
public void Action(Customer customer, OrderEventArgs e)
{
Console.WriteLine("l will serve you the dish-{0}.",e.DishName);
double price = 10;
switch (e.Size)
{
case "small":
price = price * 0.8;
break;
case "large":
price = price * 1.2;
break;
default:
break;
}
customer.Bill += price;
}
}
}
- 事件只能用在+=/-=操作符的左边,不能用在其他操作符的左边。
- 为了防止委托字段有可能在类的外面滥用,微软推出了事件。
- 事件的本质是委托字段的一个包装器。
EventHandler委托是最常用的委托(平台已经准备好了的),使用EventHandler实现上述案例:
using System;
using System.Threading;
namespace event1
{
/// <summary>
/// 事件EventHandler使用
/// 点菜的案例:点菜事件是顾客为主体对象
///
/// 事件的拥有者customer
/// 事件customer.Order
/// 事件的响应者waiter
/// 事件处理器waiter.Action
/// 事件订阅customer.Order += waiter.Action;
/// </summary>
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer();
Waiter waiter = new Waiter();
customer.Order += waiter.Action;
//触发事件,一定是事件拥有者的一些内部逻辑触发了事件
customer.Action();
customer.PayTheBill();
Console.ReadKey();
}
}
//按照规定,如果class是为了传递事件信息,应该是事件的名字+EventArgs
//如果一个类作为EventArgs使用,它需要派生于EventArgs
public class OrderEventArgs : EventArgs
{
public string DishName { get; set; }
public string Size { get; set; }
}
public class Customer
{
//事件简化声明,这里的order是一个事件,事件里包含着一个委托字段
public event EventHandler Order;
//付账的钱
public double Bill { get; set; }
public void PayTheBill()
{
Console.WriteLine("l will pay ${0}", this.Bill);
}
//添加一些方法来触发逻辑、事件
public void Walkln()
{
Console.WriteLine("walk into the restaurant.");
}
public void SitDown()
{
Console.WriteLine("Sit down.");
}
public void Think()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Let me think...");
Thread.Sleep(1000);
}
if (this.Order != null)//这里使用事件名来代替前面的字段名,这样用情非得已
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Yu";
e.Size = "large";
//Invoke是间接调用这个委托,this表示当前这个类,
this.Order.Invoke(this, e);
}
}
public void Action()
{
Console.ReadKey();
this.Walkln();
this.SitDown();
this.Think();
}
}
//事件的响应器
public class Waiter
{
public void Action(object sender, EventArgs e)
{
//类型转换
Customer customer = sender as Customer;
OrderEventArgs orderlnfo = e as OrderEventArgs;
Console.WriteLine("l will serve you the dish-{0}.", orderlnfo.DishName);
double price = 10;
switch (orderlnfo.Size)
{
case "small":
price = price * 0.8;
break;
case "large":
price = price * 1.2;
break;
default:
break;
}
customer.Bill += price;
}
}
}
- 事件本不可以使用!=和.运算符,但是这里简略声明的时候把委托字段给隐藏了,所以迫不得已使用事件。
- 面试题,为什么事件是基于委托的:source事件拥有者,subscriber事件响应者处理者
- 属性不是字段,很多时候属性是字段的包装器,包装器保护字段不被滥用;
事件不是委托字段,是委托字段的包装器,保护委托字段不被滥用;
包装器永远都不可能是被包装的东西
11、类的继承、重写、多态
11.1 继承的纵向扩展

1、父类的信息完全可以在子类找全;
2、用sealed修饰的类不可以成为基类,protected保护访问,只限于本类、子类、父类访问(实例不能访问);
3、C#中一个类最多只有一个基类(继承于、派生于某个基类),可以有多个基接口(实现了某个基接口);
4、子类的访问级别,不能超过父类,当子类前面什么都没有就是internal;
5、继承的本质是派生类在基类已有的成员的基础之上对基类进行横向或者纵向上的扩展;
6、横向扩展:对类成员的个数的扩充,纵向扩展:对类成员的版本更新或重写。
7、类成员的访问级别是以类的访问级别为上限的;
8、默认是private,只能当前类使用,变量名前面有下划线就是实例私有字段
using System;
namespace Inherit1
{
class Program
{
static void Main(string[] args)
{
Car car = new Car("TTT");
Console.WriteLine(car.Owner);
Console.ReadLine();
}
}
class Vehicle
{
public Vehicle(string owner)
{
this.Owner = owner;
}
public string Owner { get; set; }
}
class Car : Vehicle
{
法一:使用:base("N/A"),将括号里面的值直接传入父类的构造器
//public Car():base("N/A")
//{
//}
//法二:将 Car构造器也加入一个owner
public Car(string owner) : base(owner)//父类的实例构造器是不被继承的
{
}
public void ShowOwner()//横向扩展
{
//base只能访问上一级的类(父类),不能访问上上级的,this表示当前类
Console.WriteLine(base.Owner);
}
}
}
11.2 继承的纵向扩展(重写、多态)
签名:方法名和参数列表。
using System;
namespace Inherit2
{
//横向扩展
class Program
{
static void Main(string[] args)
{
//Vehicle v = new Car();
//v.Run();
//多态:当你调用一个被重写的方法,能调用到继承链上最新的版本(和实例有关的版本)
//这里的实例对象是RaseCar
//Vehicle v = new RaseCar();
//v.Run();
Vehicle v = new Car();
v.Run();
Console.WriteLine(v.Speed);
Console.Read();
}
}
class Vehicle
{
private int _speed;
public virtual int Speed//属性也可以被重写
{
get { return _speed; }
set { _speed = value; }
}
public virtual void Run()
{
Console.WriteLine("l'm running");
_speed = 100;
}
}
class Car : Vehicle
{
private int _rpm;
public override int Speed { get => _rpm/100; set => _rpm= value; }
public override void Run()
{
Console.WriteLine("Car is running");
_rpm = 5000;
}
}
class RaseCar : Car
{
public override void Run()
{
Console.WriteLine("RaseCar is running");
}
}
}
12 接口、抽象类
12.1 抽象类和接口
五个基本设计原则:单一职责原则,开闭原则,替换原则,接口隔离原则,依赖反转原则。
什么算实现:方法成员,方法体就是它的实现,数据成员字段就是它的实现。
抽象类:
using System;
namespace Abstract1
{
/// <summary>
/// 为做基类而生的"抽象类"与"开放/关闭原则"
/// 为了高效协作
/// 开放/关闭原则:如果不是为了showbug或者添加新的功能,不要随意修改代码,尤其是这个类里面函数成员的代码
/// 或者说:我们应该去封装不变的、稳定的、固定的、确定的成员,把不确定的、可能变化的成员声明成抽象成员,留给子类实现
/// </summary>
class Program
{
static void Main(string[] args)
{
Vehicle vehicle = new Car();
vehicle.Run();
Console.Read();
}
//部分选择:ALT+左键
}
abstract class Student
{
//只有返回值、方法名、参数列表,没有方法体,那就是抽象方法
//一旦一个类里有抽象方法那么这个类就是抽象类,抽象类前面必须加abstract
//抽象类指里面至少有一个没有被实现的函数成员
//函数成员一般由函数类的子类实现,所以不能是private
//由于抽象类有未被实现的成员,因此抽象类不能被实例化,所以只能作为基类让别人派生,或者声明变量可以在子类中使用
abstract public void Study();
}
//Vehicle里面没用跑,两种方法:虚方法、加个run方法加个参数
abstract class Vehicle
{
方法1:加run方法
//public void Run(string type)
//{
// if (type == "Car")
// {
// Console.WriteLine("Car is running...");
// }
// else if(type== "Truck")
// {
// Console.WriteLine("Truck is running...");
// }//违反了开闭原则,对于更改的这个类是封闭的
//}
方法2:虚方法virtual
//public virtual void Run()
//{
// Console.WriteLine("Vehicle is running...");
//}
//方法3:纯虚方法,使用抽象函数,此时需要在这个类前面就上abstract,子类实现也需要加上override
public abstract void Run();
public void Stop()
{
Console.WriteLine("Stopped!");
}
}
class Car:Vehicle
{
public override void Run()
{
Console.WriteLine("Car is running...");
}
}
class Truck : Vehicle
{
public override void Run()
{
Console.WriteLine("Truck is running...");
}
}
class RaceCar : Vehicle//新加一个类,完全符合开闭原则
{
public override void Run()
{
Console.WriteLine("RaceCar is running...");
}
}
}
转化成纯虚类:
using System;
namespace Abstract1
{
class Program
{
static void Main(string[] args)
{
Vehicle vehicle = new Car();
vehicle.Run();
Console.Read();
}
//部分选择:ALT+左键
}
//如果一个抽象类都是虚函数,未实现的
abstract class VehicleBase//最抽象的
{
abstract public void Stop();
abstract public void Fill();
abstract public void Run();
}
abstract class Vehicle: VehicleBase
{
//如果使用的全是未实现的抽象类,那么除了已经重写了的,继承的虚方法不需要写
//一个个往下推,VehicleBase里面有3个未实现,这里实现了2个,还有一个继续到子类
public override void Stop()
{
Console.WriteLine("Stopped!");
}
public override void Fill()
{
Console.WriteLine("Pay and fill...");
}
}
class Car:Vehicle
{
public override void Run()
{
Console.WriteLine("Car is running...");
}
}
}
抽象类是未完全实现的类,接口是万群未实现的类。
将抽象类转化成接口:
using System;
namespace Abstract1
{
/// <summary>
/// 为做基类而生的"抽象类"与"开放/关闭原则"
/// 为了高效协作
/// 开放/关闭原则:如果不是为了showbug或者添加新的功能,不要随意修改代码,尤其是这个类里面函数成员的代码
/// 或者说:我们应该去封装不变的、稳定的、固定的、确定的成员,把不确定的、可能变化的成员声明成抽象成员,留给子类实现
/// </summary>
class Program
{
static void Main(string[] args)
{
Vehicle vehicle = new Car();
vehicle.Run();
Console.Read();
}
}
如果一个抽象类都是虚函数,未实现的
//abstract class VehicleBase//最抽象的
//{
// abstract public void Stop();
// abstract public void Fill();
// abstract public void Run();
//}
/// <summary>
/// 纯虚数类转化为接口
/// abstract class替换成interface
/// interface作为接口,本身就是public以及抽象的abstract,以及子类的override也需要去掉
/// </summary>
///
interface IVehicle//接口,一般接口以I开头再加名字
{
void Stop();
void Fill();
void Run();
//部分选择:ALT+左键
}
abstract class Vehicle : IVehicle
{
public void Stop()
{
Console.WriteLine("Stopped!");
}
public void Fill()
{
Console.WriteLine("Pay and fill...");
}
//保留一个抽象方法run下推给子类实现,由抽象方法转化成为具体类
abstract public void Run();
}
class Car :Vehicle
{
public override void Run()
{
Console.WriteLine("Car is running...");
}
}
class Truck : Vehicle
{
public override void Run()
{
Console.WriteLine("Truck is running...");
}
}
class RaceCar : Vehicle//新加一个类,完全符合开闭原则
{
public override void Run()
{
Console.WriteLine("RaceCar is running...");
}
}
}
12.2 接口、耦合
接口里面的修饰符肯定是public,都不让你写,这决定了接口的本质,本质就是服务的消费者和服务的提供者之间的契约(透明),接口供需双方都需要遵循。
接口的将要应用:参数列表多个类型的时候,
using System;
using System.Collections;
namespace Interface1
{
class Program
{
//int[]和ArrayList都接受接口类型的契约
static void Main(string[] args)
{
int[] nums1 = new int[] { 1, 2, 3, 4, 5 };
ArrayList nums2 = new ArrayList { 1, 2, 3, 4, 5, 6 };
Console.WriteLine(Sum(nums1));
Console.WriteLine(Sum(nums2));
Console.WriteLine(Avg(nums1));
Console.WriteLine(Avg(nums2));
Console.Read();
}
static int Sum(IEnumerable nums)
{
int sum = 0;
foreach (var num in nums)
{
sum += (int)num;
}
return sum;
}
static int Avg(IEnumerable nums)
{
int sum = 0,len=0;
foreach (var num in nums)
{
sum += (int)num;
len++;
}
return sum/len;
}
}
}
依赖越直接,耦合越紧。紧耦合:
using System;
namespace Interface2
{
class Program
{
static void Main(string[] args)
{
var engine = new Engine();
var car = new Car(engine);
car.Run(3);
Console.WriteLine(car.Speed);
Console.ReadKey();
}
}
class Engine
{
public int RPM { get;private set; }
public void Work(int gas)
{
this.RPM = 1000 * gas;
}
}
class Car
{
private Engine _engine;
//此时Car这个类已经紧耦合在Engine这个类上,如果Engine这个类出问题Car就出问题
public Car(Engine engine)//构造器,初始化的时候必须要有一个engine,否则车就没有发动机
{
_engine = engine;
}
public int Speed { get; private set; }
public void Run(int gas)//车子动起来就是发动机转起来
{
_engine.Work(gas);
this.Speed = _engine.RPM / 100;
}
}
}
解耦合,松耦合:
using System;
namespace Interface3IPhone
{
/// <summary>
/// 松耦合
/// </summary>
class Program
{
static void Main(string[] args)
{
//var user = new PhoneUser(new NokiaPhone());
var user = new PhoneUser(new EricssonPhone());
//如果NokiaPhone坏了需要换成EricssonPhone,只需要在这里修改就好。
user.UsePhone();
Console.Read();
}
}
class PhoneUser//手机使用者
{
//既然是手机使用者,那必须要有一个手机
private IPhone _phone;
public PhoneUser(IPhone phone)//初始化构造器传入一个手机
{
_phone = phone;
}
public void UsePhone()
{
_phone.Dail();
_phone.PickUp();
_phone.Receive();
_phone.Send();
}
}
interface IPhone//接口
{
void Dail();
void PickUp();
void Send();
void Receive();
}
//两个手机类
class NokiaPhone : IPhone
{
public void Dail()
{
Console.WriteLine("NokiaPhone is calling...");
}
public void PickUp()
{
Console.WriteLine("Hello!This is RedMi1");
}
public void Receive()
{
Console.WriteLine("NokiaPhone message ring...");
}
public void Send()
{
Console.WriteLine("Hello!"+DateTime.Now);
}
}
class EricssonPhone : IPhone
{
public void Dail()
{
Console.WriteLine("EricssonPhone is calling...");
}
public void PickUp()
{
Console.WriteLine("Hello!This is RedMi2");
}
public void Receive()
{
Console.WriteLine("EricssonPhone message ring...");
}
public void Send()
{
Console.WriteLine("Hello!" + DateTime.Now);
}
}
}
12.3 接口、单元测试
- 接口方法是纯虚方法,抽象方法是实现了一部分但是没有全部实现,其它方法是由方法体;
- 接口无论用什么(抽象类或者其他类)来实现,方法的修饰符都是public;
- 接口方法到抽象方法,是public abstract,到其它方法是public,抽象方法往下是override·;
- 依赖关系(耦合关系):服务的使用者依赖在服务的提供者之上,依赖越直接,耦合越紧密。当我们的服务提供者出问题,那么服务使用者也会出问题。
- 依赖反转原则:解耦。单元测试是依赖反转在开发中的直接应用。
- 车依赖于人,被依赖的在下面,driver里面有个car这两个是紧耦合的,car里面有个run方法,trucker和truck是紧耦合的,racer和racecar是紧耦合的;
- 使用多态原则,里面有run方法,从图上看箭头反转了依赖反转,但是类和接口之间也是紧耦合;
- 当多个服务的提供者和使用者都遵循一个接口的时候,就可以实现多种配对;
- 在进一步就是各种设计模式。
电风扇案例:
using System;
namespace Interface4UnitTesting
{
/// <summary>
/// 单元测试:生产电扇,电扇里面有个电源,转速高电源的电流就大,还有电流保护
/// 依赖关系:电扇依赖在电流上
/// 先紧耦合,再用接口松开,再单元测试
/// </summary>
class Program
{
static void Main(string[] args)
{
var fan = new DeskFan(new PowerSupply());
Console.WriteLine(fan.Work());
Console.ReadLine();
}
}
public interface IPowerSupply
{
int GetPower();//将下面所有耦合的改为接口类型
}
public class PowerSupply :IPowerSupply//电源输出类
{
public int GetPower()
{
return 100;
}
}
public class DeskFan//电扇类
{
private IPowerSupply _powerSupply;//紧耦合
public DeskFan(IPowerSupply powerSupply)//构造器,传入一个电源
{
_powerSupply = powerSupply;
}
public string Work()
{
int power = _powerSupply.GetPower();
if (power <= 0)
{
return "Won't work.";
}
else if (power<100)
{
return "slow";
}
else if (power<200)
{
return "Work fine.";
}
else
{
return "Warning!";
}
}
}
}
单元测试超链接:刘铁锰单元测试
12.4 单一职责原则、接口隔离原则
单一职责原则:一个类只做一件事;
接口隔离原则:把本质不同的功能隔离开,再用接口给封装。乙方不能多给(基本实现),甲方不能多要,传进来的接口类型是否存在一直没有被调用的函数成员,就可以说这个接口太胖(大)了。
1、胖接口是由于设计失误,传的太多了,这个类就做了不止一件事,违反了单一职责原则。
开车,加入坦克的案例(违反了接口隔离原则):
using System;
namespace Interfacee5
{
class Program
{
static void Main(string[] args)
{
var driver = new Driver(new Car());
driver.Drive();
//此时如果想要使用tank的类,那么就需要对Driver类修改,违反了接口隔离原则
Console.Read();
}
}
class Driver//驾驶员的类
{
private IVehicle _vehicle;
public Driver(IVehicle vehicle)
{
_vehicle = vehicle;
}
public void Drive()
{
_vehicle.Run();
}
}
interface IVehicle//本来可以驾驶的车辆的类
{
void Run();
}
class Car : IVehicle
{
public void Run()
{
Console.WriteLine("Car is running...");
}
}
class Truck : IVehicle
{
public void Run()
{
Console.WriteLine("Truck is running...");
}
}
interface ITank//加上tank的类
{
void Fire();
void Run();
}
class LightTank : ITank
{
public void Fire()
{
Console.WriteLine("Boom!");
}
public void Run()
{
Console.WriteLine("Ka ka ka...");
}
}
class MediumTank : ITank
{
public void Fire()
{
Console.WriteLine("Boom!");
}
public void Run()
{
Console.WriteLine("Ka! ka! ka!...");
}
}
class HeavyTank : ITank
{
public void Fire()
{
Console.WriteLine("Boom!!!");
}
public void Run()
{
Console.WriteLine("Ka!! ka!! ka!!...");
}
}
}
C#中一个接口可以继承多个接口。
将tank接口一分为二,并采用继承其它接口的方法:
using System;
namespace Interfacee5
{
class Program
{
static void Main(string[] args)
{
var driver = new Driver(new MediumTank());
driver.Drive();
//此时如果想要使用tank的类,那么就不需要对Driver类修改
Console.Read();
}
}
class Driver//驾驶员的类
{
private IVehicle _vehicle;
public Driver(IVehicle vehicle)
{
_vehicle = vehicle;
}
public void Drive()
{
_vehicle.Run();
}
}
interface IVehicle//本来可以驾驶的车辆的类
{
void Run();
}
class Car : IVehicle
{
public void Run()
{
Console.WriteLine("Car is running...");
}
}
class Truck : IVehicle
{
public void Run()
{
Console.WriteLine("Truck is running...");
}
}
interface IWeapon//将tank这个胖接口一分为二
{
void Fire();
}
interface ITank:IVehicle,IWeapon//一个接口对其它接口的继承
{
}
class LightTank : ITank
{
public void Fire()
{
Console.WriteLine("Boom!");
}
public void Run()
{
Console.WriteLine("Ka ka ka...");
}
}
class MediumTank : ITank
{
public void Fire()
{
Console.WriteLine("Boom!");
}
public void Run()
{
Console.WriteLine("Ka! ka! ka!...");
}
}
class HeavyTank : ITank
{
public void Fire()
{
Console.WriteLine("Boom!!!");
}
public void Run()
{
Console.WriteLine("Ka!! ka!! ka!!...");
}
}
}
2、有两个设计很好的小接口合并成一个大接口,本来只需要传一个小接口,结果传了大接口。此时,就会把本来合格的案例给挡在外面了。
案例一,上诉例子将driver的类修改将小接口IVehicle换成大接口ITank,会导致IVehicle下的两种车型消失,只能访问到tank下的三种车型;
案例二,创建一个集合并未实现ICollection接口,只实现了IEnumerable接口
using System;
using System.Collections;
namespace Interfacee6Nums
{
/// <summary>
/// 2、胖接口的优化
/// 创建一个集合并未实现ICollection接口,只实现了IEnumerable接口
/// 调用者不应该多要,传进来的不应该有用不着的功能
/// </summary>
class Program
{
static void Main(string[] args)
{
int[] nums1 = { 1, 2, 3, 4, 5 };
ArrayList nums2 = new ArrayList { 1, 2, 3, 4, 5 };
Console.WriteLine(Sum(nums1));
Console.WriteLine(Sum(nums2));
var nums3 = new ReadOnlyCollection(nums1);
Console.WriteLine(Sum(nums3));
//此时nums3就用不了ICollection,只能用IEnumerable接口
foreach (var n in nums3)
{
Console.WriteLine(n);
}
Console.ReadKey();
}
static int Sum(IEnumerable nums)
{
int sum = 0;
foreach (var num in nums)//就是拿到Enumerator,对Current进行遍历
{
sum += (int)num;
}
return sum;
}
}
// 创建一个集合并未实现ICollection接口,只实现了IEnumerable接口
//IEnumerable接口需要实现GetEnumerable()方法,这个方法返回一个IEnumerator
class ReadOnlyCollection : IEnumerable
{
private int[] _array;
public ReadOnlyCollection(int[] array)//初始化器,传入一个数组
{
_array = array;
}
public IEnumerator GetEnumerator()//接口IEnumerable内部实现,需要返回一个IEnumerator
{
return new Enumerator(this);//将当前类传入
}
//成员类的引入,防止污染整个命名空间
public class Enumerator : IEnumerator
{
private ReadOnlyCollection _collection;
private int _head;//传出去的就是head的index所指的元素
public Enumerator(ReadOnlyCollection collection)
{
_collection = collection;
_head = -1;
}
public object Current
{
get
{
object o = _collection._array[_head];
return o;
}
}
public bool MoveNext()//是否到达最后一个
{
if (++_head < _collection._array.Length)
{
return true;
}
else
{
return false;
}
}
public void Reset()//重置
{
_head = -1;
}
}
}
}
案例三,接口的显示实现(C#独有的功能)
using System;
namespace Interface7XianShi
{
/// <summary>
/// 接口的显示表示,C#独有
/// </summary>
class Program
{
static void Main(string[] args)
{
var wk = new WarmKiller();
wk.Love();//此时可以看到love和kill,从设计上并不想被别人看到kill
//这里将下面的 public void Kill()修改为显示
IKiller killer = new WarmKiller();
//IKiller killer = wk;
//var w = (IGentleman)killer;
killer.Kill();//强制类型转换也可以得到love的方法
}
}
interface IGentleman
{
void Love();
}
interface IKiller
{
void Kill();
}
class WarmKiller : IGentleman, IKiller
{
public void Love()
{
Console.WriteLine("l will love you for ever...");
}
void IKiller.Kill()
{
Console.WriteLine("Let me kill the enemy...");
}
}
}
12.5 反射
反射(镜像差不多的意思):在不使用new、不知道此对象具体静态类型的情况下,创建一个同类型的对象,还可以访问这个对象带有的成员 ,可以起到进一步解耦。
反射案例,与接口结合:
using System;
using System.Reflection;
namespace Interface_Reflex1
{
class Program
{
static void Main(string[] args)
{
ITank tank = new HeavyTank();
//===============华丽的分割线==============
var t = tank.GetType();//type是对当前对象静态类型的一些描述
object o = Activator.CreateInstance(t);
MethodInfo fireMi = t.GetMethod("Fire");
MethodInfo runMi = t.GetMethod("Run");//从t里得到对应的Method
fireMi.Invoke(o, null);//在这个o的对象上调用fireMi
runMi.Invoke(o, null);//在这个o的对象上调用runMi
}
}
class Driver//驾驶员的类
{
private IVehicle _vehicle;
public Driver(IVehicle vehicle)
{
_vehicle = vehicle;
}
public void Drive()
{
_vehicle.Run();
}
}
interface IVehicle//本来可以驾驶的车辆的类
{
void Run();
}
class Car : IVehicle
{
public void Run()
{
Console.WriteLine("Car is running...");
}
}
interface IWeapon//将tank这个胖接口一分为二
{
void Fire();
}
interface ITank : IVehicle, IWeapon//一个接口对其它接口的继承
{
}
class HeavyTank : ITank
{
public void Fire()
{
Console.WriteLine("Boom!!!");
}
public void Run()
{
Console.WriteLine("Ka!! ka!! ka!!...");
}
}
}
反射的用途,依赖注入:
using System;
using Microsoft.Extensions.DependencyInjection;
namespace Interface_Reflex1
{
/// <summary>
/// 容器,直接api调用
/// java中叫自动连线
/// </summary>
class Program
{
static void Main(string[] args)
{
var sc = new ServiceCollection();//容器
sc.AddScoped(typeof(ITank),typeof(HeavyTank));//接口类型
//ITank是静态类型,typeof(ITank)拿到动态类型描述
var sp = sc.BuildServiceProvider();
//==============以上是一次性注册=========
//依赖注入,后面只要是sp,不需要使用new,初始化的都是HeavyTank
ITank tank = sp.GetService<ITank>();
tank.Fire();
tank.Run();
//好处:如果哪天程序升级了,HeavyTank变了,只需要改sc里面的HeavyTank就可以
sc.AddScoped(typeof(IVehicle), typeof(Car));
sc.AddScoped<Driver>();//将IVehicle、Driver一次性注入
var sp1 = sc.BuildServiceProvider();
var driver = sp1.GetService<Driver>();
//之前需要先定义一个IVehicle,然后再定义Driver才可以生成,现在不需要
driver.Drive();
}
}
class Driver//驾驶员的类
{
private IVehicle _vehicle;
public Driver(IVehicle vehicle)
{
_vehicle = vehicle;
}
public void Drive()
{
_vehicle.Run();
}
}
interface IVehicle//本来可以驾驶的车辆的类
{
void Run();
}
class Car : IVehicle
{
public void Run()
{
Console.WriteLine("Car is running...");
}
}
class Truck : IVehicle
{
public void Run()
{
Console.WriteLine("Truck is running...");
}
}
interface IWeapon//将tank这个胖接口一分为二
{
void Fire();
}
interface ITank : IVehicle, IWeapon//一个接口对其它接口的继承
{
}
class LightTank : ITank
{
public void Fire()
{
Console.WriteLine("Boom!");
}
public void Run()
{
Console.WriteLine("Ka ka ka...");
}
}
class MediumTank : ITank
{
public void Fire()
{
Console.WriteLine("Boom!");
}
public void Run()
{
Console.WriteLine("Ka! ka! ka!...");
}
}
class HeavyTank : ITank
{
public void Fire()
{
Console.WriteLine("Boom!!!");
}
public void Run()
{
Console.WriteLine("Ka!! ka!! ka!!...");
}
}
}
12.6 反射案例婴儿车
反射用途,更松的耦合,婴儿车的案例,主体程序:
- 创建一个.net core的控制台应用,这里取名Interface_BabyStroller.App;
- 创建一个.net core的类库,这里取名为BabyStroller.SDK。删除自带的class,右击加入接口类型IAnimal,代码为:
using System; using System.Collections.Generic; using System.Text; namespace BabyStroller.SDK { public interface IAnimal { void Voice(int timws); } }
再加入UnfinishedAttribute类
using System; using System.Collections.Generic; using System.Text; namespace BabyStroller.SDK { public class UnfinishedAttribute:Attribute { } }
此处右击重新生成解决方案。
-
在Interface_BabyStroller.App中引用BabyStroller.SDK中的dll(在bin\Debug找);
- 打开该文件的bin\Debug\netcoreapp3.1(可能是其他的),创建一个Animals文件夹;
- 创建一个.net core的类库,这里取名为BabyStrolle_Animals.Lib,再在里面创建一个Animals.Lib2的项目,分别在里面分别新建2个动物的类,分别引用BabyStroller.SDK中的dll(在bin\Debug找效果如下:
BabyStrolle_Animals.Lib的cat代码:其中cat加入未更新的方法using BabyStroller.SDK; using System; namespace BabyStrolle_Animals.Lib { [Unfinished]//如果没有更新完,就加上这个 public class Cat : IAnimal { public void Voice(int times) { for (int i = 0; i < times; i++) { Console.WriteLine("Meow!"); } } } }
BabyStrolle_Animals.Lib的sheep代码:
using BabyStroller.SDK; using System; namespace BabyStrolle_Animals.Lib { public class Sheep : IAnimal { public void Voice(int times) { for (int i = 0; i < times; i++) { Console.WriteLine("Baa..."); } } } }
Animals.Lib2的cow代码:
using BabyStroller.SDK; using System; namespace Animals.Lib2 { public class Cow:IAnimal { public void Voice(int times) { for (int i = 0; i < times; i++) { Console.WriteLine("Moo!"); } } } }
Animals.Lib2的Dog代码:
using BabyStroller.SDK; using System; namespace Animals.Lib2 { public class Dog : IAnimal { public void Voice(int times) { for (int i = 0; i < times; i++) { Console.WriteLine("Woof!"); } } } }
- 将BabyStrolle_Animals.Lib和Animals.Lib2的项目重新生成后的dll文件放在主程序bin\Debug\netcoreapp3.1(可能是其他的)\Animals文件夹中;
- 主程序代码如下所示:
using System; using System.Collections.Generic; using System.IO; using System.Runtime.Loader; using BabyStroller.SDK; using System.Linq; namespace Interface_BabyStroller.App { /// <summary> /// 基于反射婴儿车的制造 /// 可以在Animals这个文件夹里加入dll文件,其中每个类都有voice这个类 /// 为了第三方更轻松,一般发布一个SDK /// SDK:准备一个IAnimals接口,这个接口就一个方法就是Voice,创建对象直接转化为接口类型 /// </summary> class Program { static void Main(string[] args) { var folder= Path.Combine(Environment.CurrentDirectory,"Animals");//当前程序运行的文件夹 var files = Directory.GetFiles(folder); var animalsType = new List<Type>(); foreach (var file in files) { var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(file); //AssemblyLoadContext.Default 表示运行时的默认上下文,该上下文用于应用程序主程序集及其静态依赖项。 var types = assembly.GetTypes(); foreach (var t in types) {//第一包含IAnimal接口,第二UnfinishedAttribute要是false if (t.GetInterfaces().Contains(typeof(IAnimal))) { var isUnfinished = t.GetCustomAttributes(false).Any(a => a.GetType() == typeof(UnfinishedAttribute)); if (isUnfinished) continue; animalsType.Add(t); } } } while (true) { for (int i = 0; i < animalsType.Count; i++) { Console.WriteLine($"{i + 1}.{animalsType[i].Name}"); } Console.WriteLine("========================"); Console.WriteLine("Please choose animal:"); int index = int.Parse(Console.ReadLine());//写入序号 if (index > animalsType.Count || index < 1) { Console.WriteLine("No such an anima.Try again!"); continue;//超过就直接跳过本次循环 } Console.WriteLine("How many times?"); int times = int.Parse(Console.ReadLine()); var t = animalsType[index - 1]; var m = t.GetMethod("Voice"); var o = Activator.CreateInstance(t); //m.Invoke(o, new object[] { times }); var a = o as IAnimal;//强制类型转换 a.Voice(times); } } } }
13 泛型,partial类,枚举,结构体
13.1 泛型
1、案例一,未用泛型的开店案例,会导致类型膨胀:
using System;
namespace Generic1
{
/// <summary>
/// 店里卖苹果还有书,整合成泛型
/// 此时有两个产品,每个产品都有各自的盒子
/// 问题:类型膨胀,如果有一千个产品,需要一千个盒子
/// </summary>
class Program
{
static void Main(string[] args)
{
Apple apple = new Apple() { Color = "Red" };
AppleBox box = new AppleBox() { Cargo = apple };
Console.WriteLine(box.Cargo.Color);
Book book = new Book() { Name = "New Book" };
BookBox bookBox = new BookBox() { Cargo = book };
Console.WriteLine(bookBox.Cargo.Name);
}
}
class Apple//一个苹果的类
{
public string Color { get; set; }
}
class Book//一个书的类
{
public string Name { get; set; }
}
class AppleBox//一个用来装苹果的盒子
{
public Apple Cargo { get; set; }
}
class BookBox//一个用来装书的盒子
{
public Book Cargo { get; set; }
}
}
使用泛型,将成员膨胀给取消,效果如下:
using System;
namespace Generic1
{
/// <summary>
/// 店里卖苹果还有书,整合成泛型
/// 此时有两个产品,每个产品都有各自的盒子
/// 问题:类型膨胀,如果有一千个产品,需要一千个盒子
/// </summary>
class Program
{
static void Main(string[] args)
{
Apple apple = new Apple() { Color = "Red" };
Book book = new Book() { Name = "New Book" };
Box<Apple> box1 = new Box<Apple>() { Cargo=apple};
//Box后面的尖括号加的是类型参数,是一个类名,不能写成变量名
//泛型特化后,里面用到的类型参数就都是强类型的了
Box<Book> box2 = new Box<Book>() { Cargo = book };
Console.WriteLine(box1.Cargo.Color);
Console.WriteLine(box2.Cargo.Name);
}
}
class Apple//一个苹果的类
{
public string Color { get; set; }
}
class Book//一个书的类
{
public string Name { get; set; }
}
//把普通类改成泛型类的方法
//在方法名后加<>,里面放的是类型参数,是个泛化的类型
class Box<TCargo>//简单地说就是把一个类型传进来
{
public TCargo Cargo { get; set; }
}
}
2、案例二,泛型接口的案例,特化
using System;
namespace Generic2
{
class Program
{
/// <summary>
/// 泛型接口的案例
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
Student<int> stu = new Student<int>();
stu.id = 100;
stu.Name = "haoduan";
Student<ulong> stu2 = new Student<ulong>();
stu2.id = 10000000000000001;
stu2.Name = "haochang";
}
}
interface IUnique<Tid>
{
Tid id { get; set; }
}
/// <summary>
/// 这里实现了泛型类,也可以将泛型接口特例化,学生类就不再是泛型类了
/// 如:class Student : IUnique<int>
/// </summary>
/// <typeparam name="Tid"></typeparam>
class Student<Tid> : IUnique<Tid>
{
public Tid id { get; set; }
public string Name { get; set; }
}
}
3、案例三,不同类型的有序数组,组合在一起成为一个有序数组,
using System;
namespace Generic_sort
{
class Program
{
static void Main(string[] args)
{
int[] a1 = { 1, 2, 3, 4, 5 };
int[] a2 = { 1, 2, 3, 4, 5,6 };
double[] a3 = { 1.1, 2.0, 3.0, 4.0, 5.2 };
double[] a4 = { 1.1, 2.0, 3.0, 4.0, 5.2, 6.7 };
//var res = Zip(a1, a2);
var res = Zip(a3, a4);//将两个数组排在一个数组里
//这里不需要显示的将double写出来,因为参数的类型已经确定了
//var res = Zip<double>(a3, a4);
Console.WriteLine(string.Join("," ,res));
}
static T[] Zip<T>(T[] a, T[] b)//两个T[]的类型传入,并且传出一个T[]类型的值
{
T[] zipped = new T[a.Length + b.Length];
int ai = 0, bi = 0, zi = 0;
do
{
if (ai < a.Length)
zipped[zi++] = a[ai++];
if (bi < b.Length)
zipped[zi++] = b[bi++];
} while (ai < a.Length || bi < b.Length);
return zipped;
}
}
}
4、泛型委托实现Linq查询
泛型委托案例,action、func
using System;
namespace Generic_delegate
{
class Program
{
/// <summary>
/// Action委托,无返回值,<>是参数列表
/// Func委托,有返回值,<>前面是参数列表后面是返回列表
/// </summary>
static void Main(string[] args)
{
Action<string> a1 = Say;
//<>是参数列表,传入一个string类型值
a1.Invoke("baobei");//间接调用
a1("baobei");//间接调用省略方式
//Say("bao");//直接调用
Action<int> a2 = Mul;
//泛型委托的代表就是没有返回值的action
a2(1);
Func<int, int, int> func1 = Add;//前面两个是传入的参数,第三个是返回值的类型
Func<double, double, double> func2 = Add;
var res = func1(100, 200);
var res1 = func2(10.0, 20.2);
Console.WriteLine(res);
Console.WriteLine(res1);
}
static void Say(string str)
{
Console.WriteLine($"hello,{str}!");
}
static void Mul(int x)
{
Console.WriteLine(x*100);
}
static int Add(int a,int b)
{
return a + b;
}
static double Add(double a, double b)
{
return a + b;
}
}
}
lambda表达式的引用,可以取消类的使用,减少对命名空间的污染
using System;
namespace Generic_delegate
{
class Program
{
static void Main(string[] args)
{
Func<int, int, int> func1 = (a, b) => { return a + b; };
Func<double, double, double> func2 = (a, b) => { return a + b; };
//减少对命名空间的污染,把这方法名保留下来,给重要的方法用,
var res = func1(100, 200);
var res1 = func2(10.0, 20.2);
Console.WriteLine(res);
Console.WriteLine(res1);
}
}
}
13.2 partial类、枚举、结构体
- C#允许一个类的不同部分用不同语言编写,而且以自己的速度进行版本更新
- partial类可以减少派生类
- 结构体是值类型,与值类型变量相关联的那块内存存储的就是这个值类型的实例。
枚举类型、结构体类型内容及案例:
using System;
namespace Enum
{
class Program
{
static void Main(string[] args)
{
Person person = new Person();
person.Level = Level.Employee;
//通过按位或,或者相加实现同时取出枚举多个值的效果
person.Skill = Skill.Drive | Skill.Cook | Skill.Program | Skill.Teach;
Person boss = new Person();
boss.Level = Level.Boss;
Console.WriteLine(boss.Level>person.Level);
Console.WriteLine((int)Level.Boss);
Console.WriteLine(person.Skill);//查看技能
Console.WriteLine((person.Skill&Skill.Cook)>0);//如果大于0,就会cook
Student student = new Student() { ID = 113, Name = "mumuxi" };
//值类型是直接将数据存储在栈空间中,而引用类型是将数据存储在堆空间中,同时在栈空间中存储一个对该数据的引用
//装箱,将student这个实例进行Copy,放在内存堆里面,然后用obj引用堆内存里面的实例
object obj = student;
//拆箱,将堆里面的变量copy后放在值类型实例中
Student student1 = (Student)obj;
Console.WriteLine(student1.Name);
}
}
/// <summary>
/// 枚举类型的本质是一些限制了范围的整数值
/// 如果枚举类型没有手动设置数值,第一个默认为0,后面依次加一
/// 枚举类型每一个值都可以设值,也可以不设置,没有值的默认为上一个值加一
/// </summary>
enum Level
{
Employee = 100,
Manager,
Boss = 200,
BigBoss,//最后一个加不加逗号无所谓
}
enum Skill//通过二进制数字之间的关系,达到同时拥有多个的效果
{
Drive = 1,
Cook = 2,
Program = 4,
Teach = 8,
}
class Person
{
public int ID { get; set; }
public string Name { get; set; }
public Level Level { get; set; }
public Skill Skill { get; set; }
}
struct Student
{
public int ID { get; set; }
public string Name { get; set; }
}
}
装箱:
- 在堆上分配内存,这些内存主要用于存储值类型的数据;
- 接着发生一次内存拷贝动作,将当前存储位置的值类型数据拷贝到堆上分配好的位置;
- 最后返回对堆上的新存储位置的引用,这里需要一块内存放地址。
拆箱:
- 首先检查已装箱的值的类型兼容目标类型。
- 接着发生一次内存拷贝动作,将堆中存储的值拷贝到栈上的值类型实例中。
- 最后返回这个新的值。
结构体类型案例:
using System;
namespace Struct
{
class Program
{
static void Main(string[] args)
{
Student student1 = new Student() { ID = 101, Name = "mumuxi" };
Student student2 = student1;
student2.ID = 102;
student2.Name = "HaHa";
Console.WriteLine($"#{student2.ID} Name:{student2.Name}");
//值类型,可以独立的变化
student1.Speak();
Student student = new Student(1, "Over");
student.Speak();
}
}
interface ISpesk//结构体可以实现接口的
{
void Speak();
}
//结构体不能有自己的基类或者基结构体,也就是说结构体不可以派生
//结构体不可以有显示的无参构造器,可以有显示的有参构造器
struct Student :ISpesk
{
public Student(int id,string name)//显示的有参构造器
{
this.ID = id;
this.Name = name;
}
public int ID { get; set; }
public string Name { get; set; }
public void Speak()
{
Console.WriteLine($"I'm #{this.ID} student {this.Name}.");
}
}
}
14 委托、Lambda表达式串讲
14.1 委托回顾
委托是一个类的数据类型,换句说就是一种类,是一种引用类型。
委托类型声明的类并不是反应生活中的事物,是用来包裹一些方法,通过委托类型实例,间接调用方法,委托类型实例就是方法的包装器、封装器。
先定义,后实例化,再使用案例:
using System;
namespace Delete_0
{
class Program
{
static void Main(string[] args)
{
MyDele myDele = new MyDele(M1);//构造器需要输入一个返回值为null,参数列表为空的函数
myDele += M1;//由单播变成多播
myDele -= M1;
//实例方法需要实例才可以使用
/*
Student student = new Student();
myDele += student.SayHello;
*/
//上诉定义了新的变量student,可以不用定义直接使用
myDele += (new Student()).SayHello;
//myDele.Invoke();//间接调用
myDele();
MyDele1 myDele1 = new MyDele1(Add);//构造器需要输入一个返回值为int,参数列表为int,int的函数
int res = myDele1(10, 20);
Console.WriteLine(res);
}
static void M1()
{
Console.WriteLine("M1 iscalled!");
}
static int Add(int x,int y)
{
return x + y;
}
}
class Student//实例方法
{
public void SayHello()
{
Console.WriteLine("Hello,I'm a student.");
}
}
delegate void MyDele();//返回值是void,不要参数列表就是()
delegate int MyDele1(int x,int y);//返回值是void,不要参数列表就是()
}
14.2 泛型委托
如果上述Add案例参数列表为不同数据类型,泛型委托可以有效的预防类的膨胀:
using System;
namespace Delete_1
{
class Program
{
static void Main(string[] args)
{
//使用泛型委托可以有效的预防类的膨胀
MyDele<int> deleAdd = new MyDele<int>(Add);
int res = deleAdd(10, 20);
Console.WriteLine(res);
MyDele<double> deleMul = new MyDele<double>(Mul);
double mulRes = deleMul(2.2, 3.3);
Console.WriteLine(mulRes);
}
static int Add(int a,int b)
{
return a + b;
}
static double Mul(double a, double b)
{
return a * b;
}
}
//委托是类类型,定要要在class外面
//第一个T是返回值类型,<T>表示是T类型的泛型,括号里是2个T类型的输入参数
delegate T MyDele<T>(T a, T b);
}
C#有自带的Action和Func,已经很强大了,所以大多数情况无需自己定义委托:
using System;
namespace Delete_2
{
class Program
{
static void Main(string[] args)
{
//Action用于没有返回值(为void)的委托
Action a = new Action(M1);//只有Action表示输入参数为空
a();
Action<string> a1 = new Action<string>(SayHello);//输入参数为一个
a1("Tom");
Action<string,string> a2 = new Action<string, string>(Say);//输入参数为两个个
a2("Tom","Tim");
Action<string, int> a3 = new Action<string, int>(Say1);//输入参数为不同类型的两个
a3("Tom", 3);
Console.WriteLine("------------------------------");
//Func用于有返回值类型的委托
Func<int, int, int> func = new Func<int, int, int>(Add);
int res = func(100, 200);
Console.WriteLine(res);
Func<double, double, double> func1 = new Func<double, double, double>(Mul);
double res1 = func1(3.1, 3.2);
Console.WriteLine(res1);
}
static void M1()
{
Console.WriteLine("M1 is called.");
}
static void SayHello(string name)
{
Console.WriteLine($"Hello,{name}.");
}
static void Say(string name, string name2)
{
Console.WriteLine($"Hello,{name} and {name2}.");
}
static void Say1(string name, int round)
{
for (int i=0;i<round;i++)
Console.WriteLine($"Hello,{name}.");
}
static int Add(int x,int y)
{
return x + y;
}
static double Mul(double x,double y)
{
return x * y;
}
}
}
14.3 委托和Lambda表达式
Lambda表达式作用:匿名方法、InIine方法。(InIine的匿名方法)
案例:
using System;
namespace Delete_3lambda
{
class Program
{
static void Main(string[] args)
{
//节省方法名,以防污染环境,就是定义在命名空间里,别人可能也会用相同的名字
//Func<int, int, int> func = new Func<int, int, int>((a, b) => { return a + b; });
Func<int, int, int> func = ((a, b) => { return a + b; });//可以不用new
int res = func(10, 20);
Console.WriteLine(res);
//func = new Func<int, int, int>((x, y) => { return x * y; });
func = ((x, y) => { return x * y; });
res = func(3, 4);
Console.WriteLine(res);
}
}
}
泛型方法结合泛型委托、Lambda表达式的案例:
using System;
namespace Delete_4lambda
{
class Program
{
static void Main(string[] args)
{
DoSomeCalc<int>((a, b) => { return a * b; }, 10, 20);
}
//DoSomeCalc<T>是泛型方法,返回值是void,有3个T类型的输入参数
//为啥第一个是T类型?
//Func<T,T,T> func代表的是一个委托,将一个方法包装起来,
//其中,第一个T是它的返回值,后面2个T是他的输入参数
static void DoSomeCalc<T>(Func<T,T,T> func,T x,T y)
{
T res = func(x, y);
Console.WriteLine(res);
}
}
}
结束了,最后那个Linq案例没写。
更多推荐
所有评论(0)