C#学习笔记(基础)

系列文章目录

C#学习笔记(入门)
C#学习笔记(基础)


文章目录


前言

C#学习笔记(刘铁猛老师课程)---- 由浅入深,由表及里,由现象到本质。
程序猿的学习能力:

  1. 看书读文档;
  2. 动手写代码;
  3. 熟练的使用搜索引擎。

一、初识类与名称空间

1. 名称空间

(1)名称空间(namespace,又叫命名空间)的使用是为了使用不同的类时不会发生冲突,但注意不要一次性将所有的名称空间都加进来。
(2)namespace是许多编程语言使用的一种代码组织的形式,通过命名空间来分类,区别不同的代码功能,避免不同的代码片段(通常由不同的人协同工作或调用已有的代码片段)同时使用时由于不同代码间变量名相同而造成冲突。
(3)名称空间是放在类库中的,类库引用是使用名称空间的物理基础。

二、类、对象、类成员简介

1. 类(class)

类是对现实世界事物进行抽象所得到的结果。

2. 对象(object)

(1)对象也叫(实例),是经过“实例化”后得到的内存中的实体;
(2)依照类,我们可以创建对象,这就是实例化;
(3)现实世界中常称为“对象”,程序中常称为“实例”;
(4)使用new操作符创建类的实例。

3. 类的三大成员

(1)属性(property):存储数据,组合起来表示类或对象当前的状态。
(2)方法(method):由C语言中函数进化而来,表示类或对象能做什么;逻辑(算法);程序 = 数据+算法 → 用算法来加工数据。
(3)事件(event):类或对象通知其他类或对象的机制,为C#特有。

4. 静态成员与实例成员

(1)静态成员在语义上表示它是“类的成员”;实例(非静态)成员在语义上表示它是“对象的成员”。
(2)静态隶属于类,实例(非静态)隶属于通过类创建出来的变量(类的对象)。
(3)绑定(Binding)指编译器怎么把一个成员与类或对象关联起来。
(4)“.”叫做成员访问操作符。

三、C#语言基本元素概览、初识类型、变量与方法、算法简介

1. C#语言基本元素

标记{关键字、操作符、标识符、标点符号、文本(字面值)}、注释与空白。

2. 方法

方法旧称函数,是处理数据的逻辑,又称“算法”。

四、详解类型、变量与对象

1. 数据类型

数据类型是数据在内存中存储时的“型号”;小内存容纳大尺寸数据会丢失精度、发生错误;大内存容纳小尺寸数据会导致浪费内存空间;编程语言的数据类型与数学中数据类型不完全相同。

2. 软件、OS和硬件

软件开发→操作系统(OS)→硬件(计算机),软件开发专家这三种都精通,是一个完整的体系,软件在OS运行,OS在计算机上运行。

3. C#语言类型

(1)强类型语言(C#):编程时受数据类型约束严格,弱类型(Java Script)则相反,受数据类型的约束很小;C语言受数据类型约束也不是很严格。
(2)C# 4.0引入dynamic(动态类型)关键词,相当于Java Script 的var关键字;来对弱类型语言的灵活性进行模仿;注意和C#中的var关键字做区分。

4. var关键字

(1)C# 中的var关键字
① var是3.5新出的一个定义变量的类型,其实也就是弱化类型的定义,var可代替任何类型,编译器会根据上下文来判断你到底是想用什么类型的,至于什么情况下用到var,我想就是你无法确定自己将用的是什么类型,就可以使用var,类似object但是效率比object高点。
② 举个例子:假如我们现在要遍历一个数组,此时我们并不知道数组中存储的数据类型是什么,那么,我们使用var关键字,就很合适。
③ 通俗的讲:var可以理解为匿名类型,可以认为它是一个声明变量的占位符;它主要用于在声明变量时,无法确定数据类型时使用。
(2)使用var定义变量的特点
① 在定义变量时,必须先给值,不能为null,也不能只定义不给值;即必须是var s = “abcd”; 的形式,而不能是: var s; s = “abcd”; 。
② 一但初始化完成,就不能再给变量赋与初始化值类型不同的值。
③ var要求是局部变量,无法使用var来定义一个全局变量,只能定义在方法的内部(因为预先不可知,所以预先不可置)。
④ 使用var定义变量和object不同,它在效率上和使用强类型方式定义变量完全一样。
⑤ 不能用来定义函数的签名,包括返回值,参数类别。

namespace DynamicSample
{
    internal class Program
    {
        static void Main(string[] args)
        {
            dynamic myVar = 100;
            Console.WriteLine(myVar);
            myVar = "HXJ";
            Console.WriteLine(myVar);

            var name = "张三";
            var age = 23;
            var sex = true;
            //获取变量的数据类型
            Type t_Name = name.GetType();
            Type t_Age = age.GetType();
            Type t_Sex = sex.GetType();
            //打印结果
            Console.WriteLine("变量name的类型是{0},变量age的类型是{1},变量sex的类型是{2}", t_Name.ToString(), t_Age.ToString(), t_Sex.ToString());
            // 变量name的类型是System.String,变量age的类型是System.Int32,变量sex的类型是System.Boolean
        }
    }
}

————————————————
版权声明:《4. var关键字》为CSDN博主「何极光」的原创文章。
原文链接:https://blog.csdn.net/qq_44034384/article/details/106579601

4. 数据类型在C#语言中的作用

(1)存储此数据类型变量所需要的内存空间大小;
(2)此类型可表示最大、最小值范围;
(3)此类型所包含的成员(方法、属性、事件等);
(4)此类型由何基类派生而来;
(5)程序运行的时候,此类型的变量分配在内存(运行内存)的什么位置。
① Stack(栈):方法(函数)调用;比较小,几兆(M);stack overflow(栈溢出);
② Heap(堆):存储对象(实例);比较大,几个G;内存泄漏(未释放内存);
(6)此类型所允许的操作(运算)。

5. C#五大数据类型

C#五大数据类型(都是由object数据类型派生而来):类(classes)、结构体(structures) 、枚举(enumerations)、接口(interfaces)、委托(delegates)。
(1)引用类型:
类:class → object类和string类最常用,所以C#将其作为关键字。
接口:interface
委托:delegate
(2)值类型:
结构体:struct → bool(true、false)、byte、char、decimal、double、float、int、long、sbyte、short、uint、ulong、ushort
枚举:enum

6. 变量

(1)变量:表面上来看,用途是存储数据;实际上,变量表示了存储位置,并且每个变量都有一个类型,以决定什么样的值能够存入变量;变量名表示对应的变量的值在内存中的位置。
(2)变量 = 以变量名所对应的内存地址为起点、以其数据类型所要求的存储空间为长度的一块内存区域。
(3)七种变量类型:静态变量(静态字段)、实例变量(成员变量、字段)、数组元素、值参数(形参、实参)、引用参数、输出形参、局部变量。
(4)狭义的变量指局部变量,因为其他变量类型都有自己的约定名称。
(5)变量的声明:有效的修饰符组合opt 类型 变量名 初始化器opt
(6)值类型变量没有实例,所谓的“实例”与变量合二为一。
变量存储到内存中是以二进制的形式,对于负数的存储形式是正数的二进制形式按位取反再加一。
(7)引用类型变量与实例的关系:引用类型变量里存储的数据是对象(实例)的内存地址(实例在堆内存上的内存地址)。
(声明引用类型变量时,内存会自动统一分配四个字节的内存,这块内存上装的就是实例的地址。)
(8)局部变量是在stack上分配内存;实例变量(成员变量、字段)是在堆上分配内存。
(9)装箱:从栈上往堆上搬东西;拆箱:从堆上往栈上搬东西。(即对应值类型和引用类型变量的转换)
(10)变量的默认值:成员变量默认为0,可不初始化;而对于局部变量必须先初始化,否则编译不通过。
(11)常量:值不可改变的变量;声明变量前加const修饰符,常量的初始化器也不能省略。

7. 动态全局和静态变量:

(1)动态全局变量:作用域为整个项目,即最终编译成可执行文件的所有文件中均可以使用动态全局变量,生命周期为从程序运行到程序退出,即贯穿整个运行时间。
(2)静态全局变量:作用域为当前文件,从定义/声明位置到文件结尾,生命周期为从程序运行到程序退出,即贯穿整个运行时间。

8. C语言中的全局变量、局部变量和静态变量:

(1)全局变量:分配的内存在静态存储区内存上面,其作用域是全局作用域,也就是整个程序的生命周期内都可以使用,同时,有些程序并不是由一个源文件构成的,可能有许多个源文件构成,全局变量只要在一个文件中定义,就可以在其他所有的文件中使用,当然,必须在其他文件使用extern关键字声明该变量
(2)局部变量:分配内存是分配在栈存储区上的,其作用域也只是在局部函数内,在定义该变量的函数内,只要出了该函数,该局部变量就不再起作用,该变量的生命周期也只是和该函数同在。
(3)全局静态变量:分配的内存与全局变量一样,也是在静态存储内存上,其生命周期也是与整个程序同在的,从程序开始到结束一直起作用,但是与全局变量不同的是,全局静态变量作用域只在定义它的一个源文件内,其他源文件不能使用它。
(4)局部静态变量:分配的内存也是在静态存储内存上的,其第一次初始化后就一直存在直到程序结束,该变量的特点是其作用域只在定义它的函数内可见,出了该函数就不可见了。
(5)总结:局部静态变量作用函数(方法)体内;全局静态变量作用于定义它的一个源文件;局部变量和静态变量的区别在于生命周期的不同。

9. object数据类型

object 带有四种方法Equals、GetHashCode、GetType、ToString;C#语言的类型体系是以object为根,任何一种数据类型都是由objec直接或间接派生出来,所以所有数据类型都会带有这四种方法。

五、方法的定义、调用和调试

1. 方法

(1)方法(成员函数)是面向对象的概念,在非面向对象语言中仍然称为函数。
(2)什么是方法?
① 方法不能独立于类(或结构体)之外。
② 方法永远都是类(或结构体)的成员。
③ 只有作为类(或结构体)的成员时才能叫方法。
④ 类(或结构体)最基本的成员只有两个:字段与方法(成员变量与成员函数),本质上还是数据+算法。
⑤ 变量和函数封装在类里面,变得更好理解、管理,更自然的反应现实世界中事物的运动规律。
⑥ 方法表示类(或结构体)“能做什么事情”。
(3)方法命名必须用动词或动词短语,C#使用帕斯卡命名法;Java 使用的是驼峰命名法。

2. 方法的声明

(1)声明方法时的修饰符用public static时为全局静态,即成为类调用的方法,无需实例化;只有public则为全局实例(非静态)变量,即实例调用的方法。
(2)声明方法时→形式参数:formal parameter;调用方法时→实际参数:argument;方法调用时实参列表要与形参列表相匹配。
(3)声明方法时:声明/定义不分家。
(4)调用方法:(实参),可理解为调用方法时的真实条件;调用方法时的argument列表要与定义方法时的parameter列表相匹配。
(5)C#是强类型语言,argument是值、parameter是变量,值与变量一定要匹配,不然编译器会报错。

3. 构造器

(1)构造器(constructor)(也叫构造函数)是类型的成员之一,是一种特殊的函数。
(2)狭义的构造器指的是“实例构造器”(instance constructor)。
(3)构造器没有返回值

4. 方法的重载

(1)方法的重载(overload):方法名相同。
(2)声明带有重载的方法:
① 方法签名(method signature)由方法的名称、类型形参(用于泛型)的个数和它的每一个形参(按从左到右的顺序)的类型和种类(值传递、引用或输出)组成
方法签名不包含返回类型
(3)实例构造器(实例构造函数)也是可以带有重载的;实例构造函数签名由构造器名称、它的每一个形参(从左到右顺序)的类型和种类(值、引用或输出)组成。
(4)重载决策:到底调用哪个重载;用于在给定了参数列表和一组候选函数成员的情况下,选择一个最佳函数成员来实施调用。

5. 如何对方法进行debug

(1)设置断点(breakpoint)
(2)观察方法调用时的call stack
(3)Step-in,step-over,step-out
(4)观察局部变量的值与变化

六、操作符详解

1. 操作符(运算符)

(1)操作符(运算符),表达式和语句都是为方法服务,操作符和操作数组成表达式+分号→组成语句,语句就是为了组成方法体(方法的算法逻辑);写代码就是再写方法体。
(2)操作符是用来操作数据的,被操作符操作(运行)的数据称为操作数。
(3)操作符的本质是函数(即算法)的“简记法”;操作符不能脱离与它关联的数据类型,可以说操作符就是与固定数据类型相关联的一套基本算法的简记法。

2. 操作符的优先级

(1)加圆括号提高表达式的优先级;圆括号可以嵌套;
(2)同优先级操作符,除了带有赋值功能的操作符,是从左向右进行运算;带有赋值功能的操作符是由右到左;
(3)与数学运算不同,计算机语言的同优先级运算没有“结合律”。
各类在这里插入图片描述

3. 基本操作符(基本运算符就是用来组成基本表达式的运算符)

基本表达式不能再拆分,表达式用来表达一定运算用途。
(1)x.y → “.” 成员访问操作符:可以访问外层名称空间中的子集名称空间;访问名称空间中的类型;访问类型的静态成员;访问对象的成员。
(2)f(x) → “()”方法调用操作符。
(3)a[x] → “[]”元素访问操作符:访问数组中的元素。
(4)x++和x-- → 后置的自增和自减。y=x++→x会先赋值再++,其他情况,如做比较时,也是先比较再++。
(5)typeof → 查看一个类型的内部结构(metadata–源数据):类型名字,属于哪个名称空间,副类有什么,包含什么方法、事件等等。
(6)default → 获取类的默认值。
(7)new → new是个关键字,作为操作符使用时帮助我们在类型中创建一个类型的实例,并且立刻调用这个实例的实例构造器;new作为关键字还有其他用处,如作为修饰符。
(8)checked和unchecked → 用来检查一个值在内存中是否有溢出。
(9)delegate → 在C#中做操作符用的很少,主要是用来声明委托。
(10)sizeof → 获取默认结构体类型的实例在内存中的字节数,如果是自定义,需要在unsafe下使用。
(11) -> → 指针在C#只能操作结构体类型。

4. 一元(单目)操作符(只有一个操作数)

(1)+(正)
(2)-(负)
(3)~(求反操作符,在二进制级别按位取反)
(4)!(取非,只能操作bool量)
(5)++x和–x → 前置的自增和自减,赋值时先自增自减再赋值,这和后置的相反;单独使用前后置++和–,效果是一样的;在有赋值运算符时就区别很大。
(6)&x → 取地址操作符
(7)*x → 引用操作符
(8)(T) → 强制类型转换操作符(显式):
① 隐式(implicit)类型转换:不丢失精度的转换、子类向父类的转换、装箱;
② 显式(explicit)类型转换:有可能丢失精度(甚至发生错误)的转换,即cast、拆箱、使用Convert类、ToString方法与各数据类型的Parse/TryParse;
③ 自定义类型转换操作符。

5. 算术操作符(与操作数有关,注意数值类型提升)

*、/、%、+、-

6. 移位操作符

<< ,>>:数据在内存中二进制类型向左或向右进行一定位数的移动;在不产生溢出的情况下,左移为此数据乘2,右移为此数据除2。

7. 关系操作符(运算结果都是bool类型)

is、as、>、<、>=、<=

8. 逻辑(位)操作符

都是操作数据的二进制结构,得到的结果也是二进制
逻辑(位)“与”AND:&
逻辑(位)“异或”XOR:^ 异或操作符,两位不一样的时候为真。
逻辑(位)“或”OR:|

9. 条件操作符AND/OR

操作bool类型,最后得到的结果也是bool值。
条件AND:&&
条件OR:||
注意:短路效应,当左边条件满足时,直接不计算后面的条件。

10. null 值合并操作符:??

可控类型

11. 条件操作符 ?:

唯一一个可以操作三个操作数的操作符,等同于if else。

12. 赋值和lambda表达式操作符

=、*=、、/=、+=、-=、<<=、>>=、&=、^=、|=、=>
简单来说就是把运算和赋值放在一起。

七、表达式,语句详解

1. 表达式

(1)对于任何一门编程语言都有基本组件:表达式、命令和声明;表达式是编程语言的核心组件;表达式是一种专门用来求值的语法实体。
(2)C#语言对表达式的定义:表达式是由一个或多个操作数,零个或多个操作符组成的序列,经过求值过后可以是单值、对象、方法或名称空间;表达式可以由字面值、方法调用、操作符和操作数,或者单一名称(变量、类型成员、方法参数、名称空间或类型)组成。
(3)表达式是算法逻辑的最基本单元,表达一定算法意图;因为操作符有优先级,所以表达式也就有优先级。
(4)复合表达式的求值:注意操作符的优先级和同优先级操作符的运算方向。

2. 语句

(1)编程就是使用语句来编写程序;语句的组成部件有表达式;语句只有高级语言才具有,低级语言:汇编、机器语言不具有;语句是高级语言的语法,汇编和机器语言只有指令(高级语言中的表达式对应低级语言中的指令,指令是CPU能读懂的),语句等价于一个或一组有明显逻辑关联的指令。不严格的讲:高级语言的程序由语句组成的,低级语言的程序是由指令组成的。
(2)语句功能:陈述算法思想,控制逻辑走向,完成有意义的动作;语句一定是出现在方法体里
(3)C#语言的语句由分号结尾,但由分号结尾的不一定都是语句。
(4)语句详解 → 三类语句:标签语句、声明语句、嵌入式语句。

3. 标签语句

总是和goto语句一起用。

4. 声明语句

局部变量声明;局部常量声明 。

5. 嵌入式语句

(1)块语句:block用于在只允许使用单个语句的上下文中编写多条语句。{ }编译器会当成一条语句;里面可以声明任何类型的语句;在块语句之前声明的变量,在块语句中能访问;但在块语句中声明的变量,出了块就不能被访问。
(2)空语句
(3)表达式语句:不是所有表达式都允许作为语句使用。
(4)选择(判断、分支)语句:包括if语句和switch语句;选择语句会根据表达式的值从若干个给定的语句中选择一个来执行。if后只有一条嵌入式语句,可不用{},要是多条,需要用块语句。if也是嵌入式语句,if里嵌套if。
(5)try…catch…finally语句:提供一种机制,用于捕捉在块的执行期间发生的各种异常。
(6)迭代(循环)语句
while语句→while(boolean-statement) embedded-statement; 按不同条件执行一个嵌入式语句零次或多次。
do while语句→do embedded-statement while(boolean-statement) ; 按不同条件执行一个嵌入式语句一次或多次。
for语句→for(int i = 0; i<10; i++) embedded-statement
foreach语句→foreach (local-variable-type identifyer in expression) embedded-statement; 集合遍历循环;用于枚举一个集合的元素,并对该集合中的每个元素执行一次相关的嵌入语句。
(7)跳转语句
continue语句 → 结束这一次循环,立刻开始一次新循环;在复合循环下一个continue只能跳出作用的当前循环,对于外层循环不起作用。
break语句 → 立刻退出循环,执行循环后的语句;在复合循环下一个break只能退出作用的当前循环,对于外层循环不起作用。
goto语句→不是主流了,用的少。
return语句→返回值是void时尽早return原则;
throw语句→在try中用。
(8)checked/unchecked语句
(9)lock语句:多线程
(11)using语句:接口
(12)yield语句:集合

八、字段、属性、索引器、常量详解

1. 字段(field)

(1)字段(field)是一种表示与对象或类型(类与结构体)关联的变量;字段是类型的成员,旧称“成员变量”;与对象关联的字段称为“实例字段”;与类型关联的字段称为“静态字段”,由static修饰。
(2)尽管字段声明带有分号,但它不是语句,语句只能出现在方法体中;字段的名字一定是动词;字段的声明在类体里面,方法体里面是局部变量。
(3)实例字段初始化的时机–对象创建时;静态字段初始化的时机–类型被加载时。

2. 属性(property)

(1)属性(property)是一种用于访问对象或类型的特征的成员,特征反映了状态;属性是字段的自然扩展,同样实例属性反映的是对象的状态,静态属性反映的是该类型的状态。
(2)属性与字段的关系:一般情况下,它们都用于表示实体(对象或类型)的状态;属性大多数情况下是字段的包装器(wrapper);建议永远使用属性(而不是字段)来暴露数据,即字段永远都是private或protected的。

3. 索引器(indexer)

它使对象能够用与数组相同的方式(即下标)进行索引。

4. 常量(constant)

常量是表示常量值的类成员;常量隶属于类型而不是对象,即没有“实例常量”,“实例常量”的角色由只读实例字段来担当。

5. 参数

(1)传值参数:参数的默认传递方式。
(2)输出参数out:用于除返回值外还需要输出的场景。
(3)引用参数ref:用于需要修改实际参数值的场景;引用参数并不创建变量的副本,使用ref修饰符显式指出此方法的副作用是改变实际参数的值。
(4)数组参数params:用于简化方法调用。
(5)具名参数:提高可读性。
(6)可选参数:参数拥有默认值。
(7)扩展方法(this参数):为目标数据类型“追加”方法。

6. static的使用

在一个类中定义字段、属性和方法时,前面加static则为静态字段、属性和方法,为类服务;不加则为实例字段、属性和方法,为对象服务,使用时必须进行实例化。

九、委托详解

1. 委托(delegate)

委托(delegate)是函数指针的“升级版”。

2. 一切皆地址

(1)变量(数据)是以某个地址为起点的一段内存中所存储的值。
(2)函数(算法)是以某个地址为起点的一段内存中所存储的一组机器语言指令。
(3)直接调用:通过函数名来调用函数,CPU通过函数名直接获得函数所在地址并开始执行,执行完返回调用函数处继续执行。
(4)间接调用:通过函数指针来调用函数,CPU通过读取函数指针存储的值获得函数所在地址并开始执行,执行完返回调用函数处继续执行。

3. 委托的简单使用

Action委托;Func委托。

4. 委托的声明(自定义委托)

(1)委托是一种类(class),类是数据类型所以委托也是一种数据类型;它的声明方式与一般的类不同:例如,public delegate double Calc(double x, double y)。
(2)注意声明委托的位置是在名称空间下,避免声明成嵌套类型;委托与所封装的方法必须“类型兼容”,返回值的数据类型一致,参数列表在个数和数据类型上一致(参数名不需要一样)。

5. 委托的一般使用

(1)实例:把方法当作参数传给另一个方法。
(2)模板方法:“借用”指定的外部方法来产生结果;相当于“填空题”,常位于代码中部,委托有返回值。
(3)回调(callback)方法:调用指定的外部方法;相当于“流水线”,常位于代码末尾,委托无返回值。

6. 多播(multicast)委托

7. 隐式异步调用

(1)同步与异步的简介:中英文的语言差异;同步:你做完了我在你的基础上接着做;异步:咱们两个同时做(相当于汉语中的“同步进行”)
(2)同步调用与异步调用的对比:每一个运行的程序是一个进程(process);每个进程可以有一个或者多个线程(thread);同步调用是在同一线程内;异步调用的底层机理是多线程;串行=同步=单线程,并行=异步=多线程。
(3)隐式多线程 vs 显式多线程:
① 直接同步调用:使用方法名;
② 间接同步调用:使用单播/多播委托的Invoke方法;
③ 隐式异步调用:使用委托的BeginInvoke;
④ 显示异步调用:使用Thread或Task。

8. 应该适时地使用接口(interface)取代一些对委托的使用。

在这里插入图片描述

十、事件详解

1. 事件(Event)

(1)通俗定义:”能够发生的什么事情“。
(2)在C#中:使对象或类具备通知能力的成员。
(3)在类的成员中:属性的功能是用来存储和访问数据的;方法的功能就是用来做事情,简单来说就是个数据加工厂,来进行逻辑运算;事件的功能(事件发生过后的效果):通知别人;事件的功能 = 通知 + 可选的事件参数(即详细信息)。
(4)事件的使用:用于对象或类间的动作协调与信息传递(消息推送)。
(5)“对象O拥有一个事件E”意味着:当事件E发生时,O有能力通知别的对象。
(6)原理:事件模型(Event model)由五个组成部分;发送–响应。
(7)事件是类的成员之一,委托是一种数据类型。

2. 事件模型组成

(1)事件的拥有者(event source,对象/类)。
(2)事件成员(event,成员):事件本身不会主动发生。
(3)事件的响应者(event subscriber,对象/类)。
(4)事件处理器(event handler,成员)-- 本质上是一个回调方法。
(5)事件订阅 – 把事件处理器与事件关联在一起,本质上是一种以委托类型为基础的”约定“。
注意:
(1)事件处理器是方法成员.
(2)挂接事件处理器的时候,可以使用委托实例,也可以直接使用方法名,这是个C#“语法糖”。
(3)事件处理器对事件的订阅不是随意的,匹配与否由声明事件时所使用的委托类型来检测。
(4)事件可以同步调用也可以异步调用。
(5)一个事件可以挂接多个事件处理器,一个事件处理器也可被多个事件挂接。

3. 事件的声明

完整声明;简略声明(字段式声明,field-like)

4. 事件的实质是委托字段的一个包装器。

5. 事件的命名约定

带有时态的动词或者动词短语;事件拥有者”正在做“什么事情,用进行时;事件拥有者”做完了“什么事情,用完成时。

6. 为什么要使用委托类型来声明事件?

(1)站在source的角度来看,是为了表明source能对外传递哪些消息。
(2)站在subscriber的角度来看,它是一种约定,是为了约束能够使用什么样签名的方法来处理(响应)事件。
(3)委托类型的实例将用于储存(引用)事件处理器。

7. 对比事件、属性和委托

(1)属性不是字段 – 很多时候属性是字段的包装器,这个包装器用来保护字段不被滥用。
(2)事件不是委托字段 – 它是委托字段的包装器,这个包装器用来保护委托字段不被滥用。
(3)包装器永远都不可能是被包装的东西。

十一、类

1. 类简介

C#语言规范中:类是一种数据结构,它可以包含数据成员(常量和字段)、**函数成员(方法、属性、事件、索引器、运算符、实例构造函数、静态构造函数和析构函数)**以及嵌套类型。类支持继承,继承是一种机制,它使派生类可以对基类进行扩展和专用化。

2. "类"是什么?

(1)”类“是一种数据结构(data structure),是抽象的结果,抽象的载体。
(2)类“是一种数据类型;类是一种引用类型,具体到每一个类都是一个自定义类型;这个类我们可以拿去声明变量,也可以创建实例,即类可作为实例的模板。
(3) “类”代表现实世界中的”种类“。

3. 构造器

(1)静态构造器:static 类名(){} 用来初始化静态成员。
(2)实例构造器:public 类名(){} 用来初始化实例成员。

十二、类的声明与访问级别

1. 可以声明类的地方

(1)在名称空间(namespace)里;
(2)声明在传统名称空间之外也可以,但实际上也在C#的全局名称空间中(一般不这样做);
(3)声明在另一个类体里面,成为一个成员类。

2. 不同语言的声明定义

在C#和JAVA中声明即定义;在C++和C中声明和定义一般是分开的,声明放在头文件里,具体定义放在源文件里。

3. 类的声明

(1)class identifier class-body (必不可少部分)
(2)类修饰符:class-modifier(可选):new、public、protected、internal、private、abstract、sealed、static。
(3)internal class Calculator = class Calculator(默认是internal而不是private)
internal 修饰的类可以在一个项目中自由访问,即一个程序集(Assembly)里,每个项目的编译结果就是一个程序集(常用的就两种,一种可执行文件.exe;一种类库.dll)。
(4)当类是一个类的成员时,才会用private修饰符。
(5)当名称空间中没有任何类暴露给外面时,名称空间也就不暴露了。
(7)名称空间不能直接包含字段、方法、语句之类的成员。

十三、类的继承、类成员访问

1. 面向对象

面向对象最显著的特征:基于类的封装、继承和多态

2. 类的继承

(1)基类和派生类一对;父类和子类是一对。
(2)所有的类型都是从Object类型显式或隐式派生出来的,class Vehicle : Object{} = class Vehicle{}
(3)一个子类的实例从语义上讲也是一个父类的实例,即多态。
(4).NET类型系统是单根的,处在所有类型的继承链顶端的是Object类型
(5)可以用一个父类类型的变量来引用一个子类类型的实例,即多态
Vehicle vehicle = new Car(); Object object = new Car()。
(7)类的前面加sealed修饰符时,该类就是封闭类,就不能当作基类使用,即无法被继承。
(8)C#中一个类最多只能只有一个基类(父类),但可以实现多个基接口
(9)子类的访问级别不能超过父类,级别可以持平或更低。
(10)继承:子类在完整接受父类的前提下,对父类进行的横向和纵向扩展;横向:指的是对类成员个数的扩充;纵向:指的是对类成员的版本更新或重写(行为改变,版本增高)。
(11)子类对父类的成员(字段、属性、方法、事件等)全盘继承,并且这些成员会在继承链上从上到下,一直传递到底。
(12)当构建一个子类对象时,父类的构造器也会被调用的。
(13)在继承过程中父类的实例构造器是不被继承的。
(14)类成员的访问级别是以类的访问级别为上限的。
(15)private修饰符限定的类成员,在其他地方是不能访问的,子类即使继承了该成员但也不能访问;
(16)protected修饰符限定的类成员,可以被其所在类的子类访问,其他地方不能访问。

3. 修饰符internal与private的使用

(1)声明命名空间、类,前面不加限制访问修饰符时,默认访问权限为internal——访问仅限于当前程序集。
(2)声明类成员(包括字段(变量)、属性和方法)默认为private)以及
结构类型
,前面不加限制访问修饰符时,默认访问权限为private——访问仅限于当前类。
(3)声明枚举类型以及接口类型,前面不加限制访问修饰符时,默认为public且只能为public(就算要把限制访问修饰符写出来,也只能写public,而不能使用其他限制访问修饰符)——访问不受限制。

4. 类修饰符

类修饰符:public、internal、 partial、abstract、sealed、static、new、protected、private、protected internal。
(1)public:访问不受限制的,所有的本程序集以及其他的程序集里面的类都能够访问。
(2)internal:本程序集内的类可以访问,即一个项目内。
(3)partial:部分类,可以将一个类分成几部分写在不同文件中,最终编译时将合并成一个文件,且各个部分不能分散在不同程序集中。
(4)abstract:修饰类的时候表示该类为抽象类,不能够创建该类的实例。修饰方法的时候表示该方法需要由子类来实现,如果子类没有实现该方法那么子类同样是抽象类;且含有抽象方法的类一定是抽象类。
(5)sealed:修饰类时表示该类不能够被继承。
(6)static:修饰类时表示该类时静态类,不能够实例化该类的对象,那么这个类也就不能够含有对象成员,即该类所有成员为静态。
(7)new:只能用于嵌套的类,表示对继承父类同名类型的隐藏。
(8)protected、private、protected internal:只能用于嵌套的类。

5. 成员修饰符

成员修饰符:public、protected、private、internal、protected internal、abstract、virtual、override、readonly、const、sealed、new。
(1)public:访问没有限制,所有的本程序集以及其他的程序集里面的对象都能够访问。
(2)protected: 自身成员以及子类成员可访问。
(3)private:只有自身成员才能够访问。
(4)internal:本程序集内的成员可以访问。
(5)protected internal 内部保护访问。只限于本程序集或是其他程序集继承的子类访问,其他不能访问。
(6)abstract:修饰方法的时候表示该方法需要由子类来实现,如果子类没有实现该方法那么子类同样是抽象类;且含有抽象方法的类一定是抽象类。abstract不能和new同时用。
(7)static 修饰构造函数时,构造函数不能含有任何参数,不能含有修饰符,构造函数不能对对象成员进行初始化操作。但是能够对静态成员进行初始化或者调用。在静态构造函数中初始化的静态成员为最终初始化结果。
(8)virtual:**修饰方法成员,表示虚方法。**父类可以含有该类的实现,子类可以覆写该函数。
(9)override:表示该方法为覆写了父类的方法。
(10)readonly:修饰字段,表示该字段为只读字段。
注意:readonly修饰引用类型时,由于操作不当,可能修改该只读对象状态。
readonly是运行时只读,内容在运行时确定,所以修改了readonly类型成员后无需重新编译即可生效。
(11)const:**修饰字段,表示该字段为只读字段。**并且在编译时必须能够明确知道该字段的值,其值是硬编码到程序中去的,修改了该类型成员后需要重新编译才能使修改生效。
Readonly不能修饰局部变量,const可以修饰局部变量。
(12)sealed: 修饰方法时表示该方法不能被覆写。同时对一个类作abstract和sealed的修饰是没有意义的,也是被禁止的。
(13)new修饰符只能用于嵌套的类,表示对继承父类同名类型的隐藏。

6. 访问修饰符

(1)类访问修饰符
① 非嵌套的类
命名空间或编译单元内的类只有public和internal两种修饰,默认是internal 。
② 嵌套的类
嵌套类型无论是类还是结构,嵌套类型的访问修饰符为public、internal、protected、private和protected internal
嵌套类型的默认访问修饰符为private。
(2)接口访问修饰符
接口访问修饰符包括public、internal、protected、private和protected internal等,默认为public,可以省略
(3)成员访问修饰符
① public 可以被任意访问。
② protected 只可以被本类和其继承子类访问。
③ internal 只可以被本组合体(Assembly,也叫程序集)内所有的类访问,组合体是C#语言中类被组合后的逻辑单位和物理单位,其编译后的文件扩展名往往是“.DLL”或“.EXE”。
④ protected internal 唯一的一种组合限制修饰符,它只可以被本程序集和其他程序集的继承子类所访问。
⑤ private 只可以被本类所访问。
⑥ 在类内部默认的修饰符为private。
————————————————
版权声明:《4. 类修饰符;5. 类成员修饰符;7. 访问修饰符》为CSDN博主「茅坤宝骏氹」的原创文章。
原文链接:https://blog.csdn.net/moakun/article/details/78638547

十四、重写和多态

1. 重写

(1)子类对父类成员的重写,成员个数没有增加,只是同一个成员的版本增加了,叫做纵向扩展。
(2)重写时父类成员前加virtual修饰符,子类重写的成员前加override修饰符;如果不加修饰符,就成了隐藏(hide),不是重写了,这样子类中会存在两个不同的成员,只是从父类继承过来的那个成员被隐藏了,隐藏一般很少用。

2. 重写与隐藏的发生条件

(1)函数成员:方法、属性、事件、索引器等。
(2)可见(public、protected):子类可以访问。
(3)签名一致:如,对于方法,是方法名和参数列表一样。

3. 多态(polymorphism)

(1)基于重写机制(virtual→override);
(2)函数成员(方法、属性、事件等)的具体行为(版本)由对象决定:当我们用一个父类类型的变量引用一个子类类型的实例,当需要调用一个方法时,这个方法最终所调用的版本是由对象的类型决定的,即调用继承链上最新的那个版本。
(3)Vehicle car = new Car(); C#支持拿父类类型的变量来引用一个子类类型的实例。一辆小汽车是一个交通工具,一个男人是一个人;但一个人不一定就是男人,所以不能反着来。
(4)C#语言的变量和对象都是有类型的,所以会有“代差”

十五、接口和抽象类

1. 类变化过程

具体类→抽象类→接口:越来越抽象,内部实现的东西越来越少。

2. 抽象类

(1)抽象类:未完全实现逻辑的类(可以有字段和非public成员,这些代表其“具体逻辑”);函数成员(方法、属性、事件等)中至少有一个没有被完全实现,这样的类成为抽象类;这个抽象类不能被实例化,因为有函数成员未被实现(没有具体行为)。
(2)未被实现的函数成员可由它的子类来实现。
(3)这个抽象类可以用来声明变量,然后引用一个子类(实现了父类未实现的方法)类型的实例,即多态。
(4)抽象类为复用而生:专门作为基类来使用,也具有解耦功能。
(5)开闭原则:封装确定的,开放不确定的,推迟到合适的子类中去实现。
(6)当一个类中的所有函数成员(方法、属性、事件等)都是抽象的,这个类就等于接口。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AbstractcClass// 为基类而生的“抽象类”与“开放/关闭原则”
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Vehicle v = new Car();// 多态
            v.Run();
            Vehicle v1 = new Truck();
            v1.Run();
        }
    }

    abstract class VehicleBase// 函数成员全是抽象的类就相当于接口
    {
        abstract public void Run1();
        abstract public void Fill1();
        abstract public void Stop1();
    }
    
    interface IVehicleBase1// 与上面纯抽象类等同,在接口中不需要用public和abstract
    {
        void Run1();
        void Fill1();
        void Stop1();
    }

    class Vehicle1:IVehicleBase1// 类 → 实现接口(完全实现)
    {
        public void Run1()
        {
        }
        public void Fill1()
        {
        }
        public void Stop1()
        {
        }
    }

    abstract class Vehicle2 : IVehicleBase1// 类 → 实现接口(不完全实现),该类还为抽象类
    {
        public void Run1()
        {
        }
        public void Fill1()
        {
        }
        public abstract void Stop1();// 该函数成员还未被实现
    }

    abstract class Vehicle// 抽象类
    {
        public void Stop()
        {
            Console.WriteLine("Stopped!");
        }
        public abstract void Run();// 未被实现的方法,无方法体
    }

    class Car:Vehicle
    {
        public override void Run()// 在子类中实现抽象父类未实现的方法(属于函数成员)
        {
            Console.WriteLine("Car is running...");
        }
    }
    
    class Truck:Vehicle
    {
        public override void Run()
        {
            Console.WriteLine("Tuck is running...");
        }
    }
}

3. C#中虚方法与抽象方法的区别

(1)虚方法的回顾
虚方法必须使用virtual修饰。
虚方法同抽象方法(abstract)一样,使用override关键字重写。
虚方法在子类中可以实现,也可以不实现。
虚方法必须有方法体哪怕是空的。
⑤ 虚方法不能使用sealed修饰,否则不能重写。
(2)抽象方法的回顾
① 抽象方法是隐式的virtual方法。
只允许在抽象类中使用抽象方法声明。
③ **因为抽象方法声明不提供实实现,所以没有方法体;方法声明只是以一个分号结束,并且在签名后没有大括号 ({ })。**例如:public abstract void MyMethod();
④ 实现由overriding方法提供,它是非抽象类的成员。
⑤ 在抽象方法声明中使用static或virtual修饰符是错误的。
(3)虚方法与抽象方法的区别
① 关键字不同抽象方法(abstract)虚方法(virtual)。
抽象方法必须在抽象类中,而虚方法不需要。
抽象方法在父类中不可以实现,虚方法可以实现。
抽象方法子类必须实现抽象方法,虚方法可以选择实现或者不实现。
(4)对于两者区别的总结
抽象方法是只有定义、没有实际方法体的函数,它只能在抽象函数中出现,并且在子类中必须重写;虚方法则有自己的函数体,已经提供了函数实现,但是允许在子类中重写或覆盖。重写的子类虚函数就是被覆盖了。
————————————————
版权声明:《3、C#中虚方法与抽象方法的区别》为CSDN博主「何极光」的原创文章。
原文链接:https://blog.csdn.net/qq_44034384/article/details/106742815

十六、接口与单元测试

1. 接口介绍

(1)接口和抽象类都是“软件工程产物”。
(2)接口:是完全未实现逻辑的“类”(“纯虚类”;只有函数成员成员全部public(隐式的,即不用写,默认是公开的))。
(3)接口为解耦而生:“高类聚,低耦合”,方便单元测试。
(4)接口是一个“协约”,有分工必有协作,有协作必有协约。
(5)接口和抽象类都不能被实例化,只能用来声明变量、引用具体类(concrete class)的实例。

在这里插入图片描述
在这里插入图片描述
未使用接口(契约)时:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

**// 未使用接口(契约)时(可能会出现重复定义一些内容,复用性低)**
namespace InterfaceExample
{
    internal class Program
    {
        static void Main(string[] args)
        {
            int[] num1 = { 1, 2, 3, 4, 5 };
            //int[] num1 = new int[] { 1, 2, 3 }; 
            ArrayList num2 = new ArrayList() { 1, 2, 3, 4, 5};// 元素类型为object
            Console.WriteLine(Sum(num1));
            Console.WriteLine(Avg(num1));

            Console.WriteLine(Sum(num2));
            Console.WriteLine(Avg(num2));
        }

        static int Sum(int[] num)// 静态方法可直接调用
        {
            int sum = 0;
            foreach (int i in num)
            {
                sum += i;                
            }
            return sum;
        }

        static double Avg(int[] num)
        {            
            int sum = 0;
            int count = 0;
            foreach(int i in num)
            {
                sum += i;
                count++;               
            }
            int avg = sum / count;
            return avg;
        }

        static int Sum(ArrayList num)// 方法重载
        {
            int sum = 0;
            foreach (var item in num)// ArrayList类型中的元素类型为object
            {
                sum += (int)item;// 需要强制转换
            }           
            return sum;
        }

        static double Avg(ArrayList num)
        {
            int sum = 0;
            int count = 0;
            foreach (var item in num)                 
            {
                sum+= (int)item;
                count++;
            }
            int avg = sum / count;
            return avg;
        }
    }   
}

使用接口时:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

**// 使用接口(契约)时**
namespace InterfaceExample
{
    internal class Program
    {
        static void Main(string[] args)
        {
            int[] num1 = { 1, 2, 3, 4, 5 };
            //int[] num1 = new int[] { 1, 2, 3 }; 
            ArrayList num2 = new ArrayList() { 1, 2, 3, 4, 5};// 元素类型为object
            Console.WriteLine(Sum(num1));
            Console.WriteLine(Avg(num1));

            Console.WriteLine(Sum(num2));
            Console.WriteLine(Avg(num2));
            
        static int Sum(IEnumerable num)// IEnumerable 接口,公开枚举数,该枚举数支持非泛型集合上进行简单迭代
        {
            int sum = 0;
            foreach (var item in num)// ArrayList类型中的元素类型为object
            {
                sum += (int)item;
            }
            return sum;
        }
 
        static double Avg(IEnumerable num)
        {
            int sum = 0;
            int count = 0;
            foreach (var item in num)
            {
                sum += (int)item;
                count++;
            }
            int avg = sum / count;
            return avg;
        }

    }   
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InterfaceExample1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var user = new PhoneUser(new NokiaPhone());
            user.UserPhone();
            var user1 = new PhoneUser(new EricssonPhone());
            user1.UserPhone();
        }
    }
    class PhoneUser
    {
        private IPhone _phone;// 加下划线表示实例私有字段
        public PhoneUser(IPhone phone)// 实例构造器
        {
            _phone = phone;
        }

        public void UserPhone()
        {
            _phone.Dial();
            _phone.Send();
            _phone.PickUp();
            _phone.Receive();
        }
    }

    interface IPhone
    {
        void Dial();
        void PickUp();
        void Send();
        void Receive();
    }

    class NokiaPhone : IPhone
    {
        public void Dial()
        {
            Console.WriteLine("Nokia calling...");
        }

        public void PickUp()
        {
            Console.WriteLine("Hello This is Tim!");
        }

        public void Receive()
        {
            Console.WriteLine("Nokia message ring...");
        }

        public void Send()
        {
            Console.WriteLine("Hello!");
        }
    }

    class EricssonPhone : IPhone
    {
        public void Dial()
        {
            Console.WriteLine("Ericsson calling...");
        }

        public void PickUp()
        {
            Console.WriteLine("Hello This is Tim!");
        }

        public void Receive()
        {
            Console.WriteLine("Nokia message ring...");
        }

        public void Send()
        {
            Console.WriteLine("Hello!");
        }
    }
}

十七、接口隔离、反射、特性、依赖注入

1. 接口隔离原则

(1)服务调用者(甲方)不会多要,即传给调用者的接口类型里有没有一直没调用到的函数成员。
(2)服务提供者(乙方):接口的实现类,必须完全实现接口的函数成员,不然就还是抽象类,不能实例化。
(3)接口:就相当于契约。

接口未隔离前:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace IspExample
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //var driver = new Driver(new Car());
            Car car = new Car();
            Driver driver = new Driver(car);
            driver.Drive();
        }
    }

    class Driver // 服务调用者,只用去调用接口中的函数成员就行,无需知道函数成员具体咋实现的。
    {
        IVehicle _vehicle;// 声明一个接口类型的变量
        public Driver(IVehicle vehicle)// 实例构造器
        {
            _vehicle = vehicle;
        }

        public void Drive()
        {
            _vehicle.Run();
        }
    }

    interface IVehicle // 契约
    {
        void Run();
    }

    interface ITank // 契约
    {
        void Run();
        void Fire();
    }

    // 服务提供者
    class Car : IVehicle
    {
        public void Run()
        {
            Console.WriteLine("car is running...");
        }
    }
    class Truck : IVehicle
    {
        public void Run()
        {
            Console.WriteLine("truck is running...");
        }
    }

    class LightTank : ITank
    {
        public void Fire()
        {
            Console.WriteLine("小型炮!");
        }

        public void Run()
        {
            Console.WriteLine("LightTank is running");
        }
    }
    class HeavyTank : ITank
    {
        public void Fire()
        {
            Console.WriteLine("重型炮!");
        }

        public void Run()
        {
            Console.WriteLine("HeavyTank is running");
        }
    }
}

接口隔离后:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace IspExample
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //var driver = new Driver(new Car());
            Car car = new Car();
            LightTank lightTank = new LightTank();
            Driver driver = new Driver(car);
            Driver driver1 = new Driver(lightTank);
            driver.Drive();
            driver1.Drive();
        }
    }

    class Driver // 服务调用者,只用去调用接口中的函数成员就行,无需知道函数成员具体咋实现的
    {
        IVehicle _vehicle;// 声明一个接口类型的变量
        public Driver(IVehicle vehicle)// 实例构造器
        {
            _vehicle = vehicle;
        }

        public void Drive()
        {
            _vehicle.Run();
        }
    }

    interface IVehicle // 契约
    {
        void Run();
    }

    interface IWeapon
    {
        void Fire();
    }
    interface ITank:IVehicle,IWeapon // 契约
    {
        
    }

    // 服务提供者
    class Car : IVehicle
    {
        public void Run()
        {
            Console.WriteLine("car is running...");
        }
    }
    class Truck : IVehicle
    {
        public void Run()
        {
            Console.WriteLine("truck is running...");
        }
    }

    class LightTank : ITank
    {
        public void Fire()
        {
            Console.WriteLine("小型炮!");
        }

        public void Run()
        {
            Console.WriteLine("LightTank is running");
        }
    }
    class HeavyTank : ITank
    {
        public void Fire()
        {
            Console.WriteLine("重型炮!");
        }

        public void Run()
        {
            Console.WriteLine("HeavyTank is running");
        }
    }
}

2. 反射与依赖注入

反射:以不变应万变(更松的耦合)

十八、泛型、部分类、枚举、结构体

1. 泛型

泛型(generic),强类型,使用时必须特化;泛化具体的数据类型,即剥离于具体的数据类型。

泛型类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace HelloGeneric
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Apple apple = new Apple() { Color = "Red" };
            Book book = new Book() { Name = "新华字典" };
            //Box appleBox = new Box() { Apple = apple };
            //Box bookBox = new Box() {  Book = book };
            //Console.WriteLine(appleBox.Apple.Color);
            //Console.WriteLine(bookBox.Book.Name);
            Box<Apple> box1 = new Box<Apple>() { Cargo = apple };// 实现泛型类时需特化
            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
    //{
    //    public Apple Apple { get; set; }
    //    public Book Book { get; set; }
    //}

    class Box<TCargo>// 泛型类
    {
        public TCargo Cargo { get; set; }
    }
}

泛型接口:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace HelloGeneric1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //Student<int> student = new Student<int>();// 实现泛型接口时需特化,int为泛型的类型参数
            //student.ID = 1;
            //student.Name = "HXJ";

            Student student = new Student();
            student.ID = 1;
            student.Name = "HXJ";
        }
    }
    
    interface IUnique<TId>
    {
        TId ID { get; set; }
    }
    //class Student<TId> : IUnique<TId>// 如果一个类实现的是一个泛型接口,则这个类就成了泛型类
    //{
    //    public TId ID { get ; set ; }
    //    public string Name { get; set; }
    //}

    class Student : IUnique<int>
    {
        public int ID { get; set ; }
        public string Name { get; set; }
    }
}

2. 集合:数组、列表、链表、字典等,这些都是泛型的。

3. partial类:减少类的派生。

4. 枚举类型:人为限定取值范围的整数;整数值的对应;比特位用法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

// 枚举类型
namespace HelloGeneric2
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Person person = new Person();
            person.Level = Level.BOSS;
            Person person1 = new Person();
            person1.Level = Level.Employee;

            Console.WriteLine(person1.Level < person.Level);
            Console.WriteLine((int)Level.BOSS);
            Console.WriteLine((int)Level.Employee);
            Console.WriteLine((int)Level.BigBOSS);
            Console.WriteLine((int)Level.Manger);

            Person person2 = new Person();
            person2.Skill = Skill.Drive | Skill.Cook | Skill.Program | Skill.Teach;// 位或,运算为1111即15
            Console.WriteLine(person2.Skill);
            Console.WriteLine((person2.Skill & Skill.Cook) > 0);// 位与:1111与0010

        }
    }
    enum Level
    {
        Employee=100,
        Manger=200,
        BOSS=300,
        BigBOSS=400,
    }
    enum Skill
    {
        Drive = 1,// 0001
        Cook = 2,// 0010
        Program = 4,//0100
        Teach = 8,//1000
    }

    class Person
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public Level Level { get; set; }
        public Skill Skill { get; set; }
    }
}

5. 结构体(struct)

(1)值类型(特点:与值类型变量相关联的那块内存里存储的就是这个值类型的实例,注意与引用类型区分),可装/拆箱。
(2)可实现接口,不能派生自类/结构体;
(3)不能有显式无参构造器。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace HelloStruct
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Student student = new Student() { ID = 101, Name = "HXJ" };
            Object obj = student;// 装箱,即可以用基类型的变量引用子类型的实例
            Student student2 = (Student)obj;// 拆箱,需要强制转换
            Console.WriteLine(student2.ID);
            Console.WriteLine(student2.Name);
            Console.WriteLine($"#{student2.ID} Name:{student2.Name}");

            Student student3 = new Student() { ID = 101, Name = "HXJ" };
            Student student4 = student3;
            Console.WriteLine(student4.ID);

            student4.Speak();
        }
    }
    interface ISpeak
    {
        void Speak();
    }
    struct Student:ISpeak// 可以实现接口
    {
        public int ID { get; set; }
        public string Name { get; set; }

        public void Speak()
        {
            Console .WriteLine($"{this.Name} and {this.ID}");
        }
    }
}

(4)结构体是一种关键字为struct声明的自定义数据类型。与类相似,也可以包含构造函数,常数,字段,方法,属性,索引器,运算符和嵌套类型等;不过,结构体是值类型,类是引用类型
① 虽然结构体与类的类型不一样,可是他们的基类型都是对象(object),C#中所有类型的的基类型都是object
② 虽然结构体的初始化也使用了New操作符,可是结构体对象依然分配在栈上而不是堆上声明了结构类型后,可以使用new运算符创建构造对象,也可以不使用new关键字,如果不使用“新建”(New),那么在初始化所有字段之前,字段将保持未赋值状态,且对象不可用。

6. 结构体与类的使用选择

(1)栈的空间有限,对于大量的逻辑的对象,创建类要比创建结构好一些。
(2)结构体适合表示如点、矩形和颜色这样的轻量对象。例如,如果声明一个含有1000个点对象的数组,则将为引用每个对象分配附加的内存。在此情况下结构体的成本较低。
(3)在表现抽象和多级别的对象层次时,类是最好的选择。
(4)加粗样式大多数情况下该类型只是一些数据时,结构体是最佳的选择。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值