1 类库的引用
(1) 类库的引用是使用名称空间的物理基础,不同技术类型的项目会默认引用不同的类库
(2) DLL引用,黑盒引用,无源代码,可用nuget打包引入
(3) 项目引用,白盒引用,有源代码
自己在solution里新建了类库,project需要引入reference里,然后程序里写using namespace
类与类,类库与类库之间是有依赖(耦合)关系的,低层类库不正常,上层也不行。程序追求高内聚低耦合
2 类与对象
(1) 对象也叫实例,是类经过实例化后得到的内存中的实体;
- 对象和实例是一回事;
- 天上有一架飞机,必须是实例飞,概念是不能飞的;
- 有些类不能实例化;
- 依照类,使用new操作符创建类的实例;
(2) 引用变量与实例的关系
- 孩子与气球
- 气球不一定有孩子牵着
- 多个孩子可以使用各自的绳子牵着同一个气球,也可以通过一根绳子牵着气球
Form myForm; //引用变量,孩子 Form myForm = new Form(); //一个孩子与一个气球; Form myForm2 = myForm; //2个孩子用各自的绳子牵着同一个气球
(3) 类的三大成员
- 属性property:存储数据,组合起来表示类或对象当前状态
- 方法method:表示类或对象能做什么,是真正做事、构成逻辑的成员
- 事件event:类或对象通知其他类或对象的机制,C#特有
某些特殊类或对象在成员方面侧重点不同,如模型类或对象侧重属性,工具类侧重方法,通知类侧重事件
(4) 静态成员于实例成员
- 静态static成员在语义上表示它是类的成员;人抽象出来的类:总数,平均身高,平均体重
- 实例(非静态)成员在语义上表示它是对象的成员;某个对象的身高、体重
- 绑定binding指的是编译器如何把一个成员与类或对象关联起来;‘.’操作符用于成员方位
快捷键:tab,可以快速补齐未写完代码的常规部分,比如for循环,事件注册等;
ctrl+E+C注释,ctrl+K+U去掉注释
3 C#基本元素
关键字,标识符(不以数字开头,用关键字的话前面加@),操作符,标点符号,文本(字面值),注释
4 类型、变量与对象
(1) 强类型:数据类型与数据强绑定,能保证数据完整性,比如将double赋值给int会报错;
dynamic类型用来模仿弱类型;
(2) 类型在C#中的作用
- 存储此类型所需的内存空间大小
- 此类型的值可表示是的最大值最小值范围
- 此类型所包含的成员(方法、属性、事件等);
- 静态程序:程序没有执行的时候,写代码或编译,编译器可以验证类型是否包含某个成员;
- 动态程序:程序执行起来,调试或运行,比如反射;
- 此类型由何种基类派生而来
- 程序运行时,此类型的变量在分配在内存的什么位置
- stack栈:方法调用,比较小,比较快;stack overflow
- heap堆:存储实例等;内存泄露
- 此类型所允许的操作(运算3/4与3.0/4区别)
(3) C#的五大数据类型
- 类class:form, windows
- 结构体struct:int, long, double
- 枚举enum
- 接口
- 委托
object为根,蓝色是基本数据类型,太常用的成为了关键字;
(4) 什么是变量
- 表面来看,变量用途是存储数据
- 变量表示了存储位置,并且每一个变量都有一个类型,以决定什么样的值能够存入变量
- 有7种变量:静态变量,实例变量(成员变量,字段),数组元素,值参数,引用参数ref,输出形参out,局部变量
- 声明变量:有效的修饰符组合(opt) 类型变量名 初始化器(opt) public static int p =10
- 变量是以变量名所对应的内存地址为起点,以其数据类型所需要的存储空间为长度的一块内存区域
(5) 值类型的变量,值类型没有实例,所谓的实例与变量合而为一
(6) 引用类型的变量与实例的关系:引用类型变量里存储的数据是实例在堆内存里的内存地址;
Student stu;//声明后,引用类型在内存中用全0的数据分配4个字节内存(内存A); stu = new Student();//实例化后,分配student类所占字节的内存B,并把此地址保存在内存A中; class Student{uint ID};
(7) 局部变量在栈上分配内存
(8) 注意变量的默认值;常量const初始化赋值,不能再次赋值;
装箱:
int x = 100; object obj = x;object引用的值不是堆上的实例而是栈上的值类型的值时,先把x的值复制封装成objet的实例,在堆上找内存放,再把此内存放在obj里。
拆箱:
int y=(int)obj; 先在栈上分配y的内存A,再把堆上obj实例的值按要求拆成目标类型存储在栈上内存A里去;
装箱与拆箱都会损失性能;
5 方法的定义、调用与调试
(1) 方法
- 方法的前身是C/C++语言的函数
- 方法是类或结构体的成员,C#中方法不可能独立于类或结构体之外,只有作为类或结构体的成员才被称为方法,C++中是可以的,称为全局函数;
- 类或结构体最基本的成员:方法与字段(成员函数与成员变量),本质还是数据+算法,方法表示能做什么事
- 方法可以:隐藏复杂的逻辑,把大算法分解为小算法,复用;
- 方法首字母大写,用动词或动词短语命名
- parameter形参,argument实参
(2) 构造器
ctor+tap+tap 自动补齐构造器代码
- 构造器是类型的一种,狭义的构造器指的是实例构造器;
- 构造器的内存原理
class Student { pubilc Student(int id, string name) {this.ID=id; this.Name = name;} pubilc int ID; public string Name; } ... Student stu = new Student(2,"no");
//创建引用变量stu,在从高往低分内存空间的栈上分配内存空间(未赋值)
//new创建实例(在堆上分配内存空间),8byte
//(2,"no")调用实例的构造函数,切割int分4byte内存A,string分4byte内存B,int设为2;
//string是class类型,需要在堆中再找一个内存C存放string的初始值"no",内存C的地址放在内存B中;内存A的地址存放在栈上stu处
(3) 方法的重载
- 方法签名由方法的名称、类型形参的个数和它的每一个形参(从左到右的顺序)的类型和种类(值,引用和输出)组成。方法签名不包含返回类型。
- 实例构造函数签名由它的每一个形参(按从左到右的顺序)的类型和种类(值、引用、输出)组成。
- 重载决策(到底调用哪一个重载):用于在给定了参数列表和一组候选函数成员的情况下,选择一个最佳函数成员来调用。
(4) 调试
-
step into:单步执行,遇到子函数就进入并且继续单步执行(简而言之,进入子函数);
-
step over:在单步执行时,在函数内遇到子函数时不会进入子函数内单步执行,而是将子函数整个执行完再停止,也就是把子函数整个作为一步。有一点,经过我们简单的调试,在不存在子函数的情况下是和step into效果一样的(简而言之,越过子函数,但子函数会执行)。
-
step out:当单步执行到子函数内时,用step out就可以执行完子函数余下部分,并返回到上一层函数。
(5) 方法调用时内存分配,stack frame 是方法被调用是在栈内存的布局
- main在栈里已经占一部分内存,把main(主调用者)里子函数(被调用者)的参数压入栈中,先压左边后压右边,谁调用谁负责;
- 进入到子函数,子函数把自己变量压到栈里
- 返回值存在cpu的寄存器里,上一层函数从寄存器中读取放在对应局部变量的内存里
- return返回后,一个函数占用的栈内存清空,返回上一层函数
6 操作符
(1) 操作符本质
- 操作符就是函数的简记法,3+4,即为Add(3,4)
- 操作符不能脱离与它关联的数据类型,可以为自定义的数据类型创建操作符
(2) 操作符优先级,除了带赋值功能的操作符运算顺序是从右往左,其他同优先级是从左到右;
(3) 基本操作符
- Type t = typeof(int); t.Name等,用来获取类型内部结构;
- default设默认值,一般为0;Form my = default(Form);表示NULL;枚举的话,数字值里要有0,要不无法匹配;
- Form my = new Form(); new操作符创建类型实例,调用实例构造器(), 得到实例的内存地址并通过=交给变量my;
- new调用初始化器,Form my = new Form(Text = "my"); new操作符创建匿名类型 var person = new{ Name="l", Age=1}:
- new做修饰符,在子类隐藏父类的方法,不常见
- checked,unchecked检查数据类型是否溢出,可以检查一个数据类型或者一段代码;
- delegate做操作符被lambda取代了,匿名函数
- sizeof,只能获取基本数据类型,即结构体类型,除了string,object;也能获取自定义的结构体类型的字节数,但要放在unsafe里;
- ->需要放在unsafe里,类似指针的结构体,间接访问成员;
- is用于判断一个对象是否可以转换为指定的类型,不会抛出异常,返回bool值用来表示是否转换成功;
- as用于执行引用类型的显式类型转换。如果要转换的类型与指定的类型兼容,转换就会成功进行;如果类型不兼容,as运算符就会返回值null
- null合并运算符??
double? num1 = null; double? num2 = 3.14157;//num2.HasValue,,,num2.Value double num3; num3 = num1 ?? 5.34; Console.WriteLine("num3 的值: {0}", num3); num3 = num2 ?? 5.34; Console.WriteLine("num3 的值: {0}", num3); Console.ReadLine(); num3 的值: 5.34 num3 的值: 3.14157
(4) 类型转换
- 隐式类型转换
- 不丢失精度的转换,比如int转double
- 子类向父类的转换,Human t = teacher h; t只能看到Huaman的方法,看不到teacher独有的;你看到的东西取决于你的身份,而不是前面有什么;
- 装箱
- 显式类型转换
- 有可能丢失精度或有错误,即cast,ushort y = (uint) x;注意符号位
- 拆箱
- Convert.ToString,或者调用实例的方法result.ToString(),或者实例的Parse解析方法,只能解析格式正确的数据;tryParse()
7 字段
(1) 什么是字段
- field是一种表示与对象或类型(类与结构体)关联的变量
- 字段是类型的成员,旧称成员变量
- 与对象相关联的称为实例字段
- 与类型关联的字段称为静态字段,static修饰,表示类型当前状态,比如平均身高等;
(2)字段的声明
- 字段不是语句,语句要写在函数体里;字段的名字一定是名词;
- 字段声明要写在类里,写在方法里就是局部变量了;
(3) 字段的初始值
- 无显示初始化时,字段获得其类型的默认值,所以字段永远都不会未被初始化;
- 实例字段初始化的时机---对象创建时;
- 静态字段初始化的时机---类型被加载时;在被运行环境加载时初始化,静态构造器static Student(){}只执行一次;一般初始化在声明的时候初始化;
(4) 只读字段
readonly修饰的字段只能在构造器里初始化,不能再次赋值;
8 属性
(1) 属性含义
- 属性时一种用于访问对象或者类型的特征的成员,特征反应了状态;
- 属性时字段的自然拓展
- field更前向与实例对象在内存中的布局,property更偏向于反映现实世界对象的特征;
- 对外:暴露数据,数据可以是存储在字段里的,属性也可以是动态计算出来的;
- 对内:保护字段不被非法值修改;
- 属性有get/set方法对进化而来;get/set,set里value是上下文关键字,在特定上下文中是关键字,value在set中代表用户传进来的值,出了set就不是了。
(2) 属性声明
- 完整声明——后台成员变量与访问器,属性修饰符多为public 或 public static;
propfull+tab+tab可以自动写出属性定义,再按tab可修改名称;
prop+tab+tab属性的简略声明public int MyProperty { get; set; }跟字段一样不能保护字段不被非法值修改;
ctrl+R+E放在你打算封装为属性的变量上,可以自动填充剩下代码;
- 简略声明——只有访问器;
- 只读方法,访问器里没有set。只写属性语法正确,不常用,因为属性的主要目的是向外暴露数据而表示对象或类型的状态;
- 动态计算值的属性,属性的名字一定是名词,也分实例属性与静态属性;
(3) 属性和字段关系
- 一般都用于表示对象或类型的状态;
- 属性大多情况下是字段的包装器
- 建议永远用属性暴露数据,字段永远是private或protected;
9 索引器
索引器能使对象能够用与数组相同的方式即用下标进行索引;
声明索引器:Index+tab+tab快速填充代码
10 常量
常量是表示常量值(编译时计算的值)的类成员,常量只能时基本数据类型;常量属于类型,实例没有常量;局部常量指的时函数体里加const修饰的;
- 提高程序效率和可读性:常量;
- 为了防止对象的值被改变:只读字段;
- 向外暴露不允许修改的数据:只读属性;
- 当希望成为常量的值其类型不能被常量声明接受是(类或自定义结构体):静态只读字段static readonly
11 参数
(1) 传值参数,参数的默认传递方式
- 值类型:值参数创建变量的副本,参数的初始值是变量的值,对值参数操作不影响变量的值;
- 引用类型并且创建新对象:创建副本,对值参数操作不影响变量的值;
- 引用类型,只操作对象不创建新对象:对象还是那个对象,但对象里的值已经改变;相当与把对象值的地址传进去了,所以修改值,外面也随之变化;
object.GetHashCode()代表对象的唯一值;某一个变量换名字,变量上按ctrl+。即可调出菜单让程序中其他地方自动修改。
(2) 引用参数,用于修改实际参数值的场景
ref修饰,并不创建新的存储位置,引用形参表示的存储位置是方法调用中作为实参给出的变量所表示的存储位置;
变量在座位引用形参传递前必须先明确赋值;
- 值类型:并不创建变量的副本,可修改实参的值;
- 引用类型,创建新对象:创建新对象后,外部实参指向了新对象的地址,改变了实参的值;
- 引用类型,不创建对象,只改变对象值:与传值参数效果相同,机理不同,传值参数创建了副本,相当于两个地址指向一个实例;ref相当于直接操作实参;
(3) 输出参数,用于除返回值外还需要输出的场景
out修饰,不创建新的存储位置。在作为输出参数传递之前不一定要明确赋值;但是在方法内部,在返回之前,方法的每个输出形参都必须明确赋值;
- 值类型:通过参数向外输出值;
- 引用类型,创建新对象:方法外的变量也引用了新创建的对象
(4) 数组参数,用于简化方法的调用
必须是形参列表中的最后一个,params修饰;
static int Add(params int[] intArray);//声明 Add(1,2,3);//调用,编译器自动声明数组并把值传进去
ref是为了改变值,out是为了输出,
(5) 具名参数,提高可读性
参数位置不受约束
static void Print(string name, int age);//声明 Print(name:"dd", age:1);//调用
(6) 可选参数
参数具有默认值,不推荐使用
(7) 扩展方法:this参数,为目标数据类型追加方法
- 方法必须是公有的静态的,被public static修饰
- 必须是形参列表总的第一个,this修饰
- 必须由一个静态类(一般类名为SomeTypeExtension)来统一收纳对SomeTypeExtension类型的扩展方法;
class Program { static void Main(string[] args) { double x = 3.1415926; double y = x.Round(3);//x就是Round里的第一个参数 } } static class DoubleExtension { public static double Round(this double input, int digits) { double result = Math.Round(input, digits); return result; } }
常用LINQ方法,using System.Linq
List<int> my = new List<int>() { 1, 2, 3, 9 }; my.All(i => i > 10);
12 委托
(1) 什么是委托
- 委托是函数指针的升级版;委托的简单使用:Action, func
- 一切皆地址:变量是以某个地址为起点的一段内存中存储的值;函数是以某个地址为起点的一段内存中所存储的一组机器语言指令;
- 直接调用:通过函数名来调用函数,CPU通过函数名直接获得函数所在地址并执行->返回;
- 间接调用:通过函数指针来调用函数,CPU通过读取函数指针存储的值获得函数所在地址并开始执行->返回;
class Program { static void Main(string[] args) { Calculator calculator = new Calculator(); Action action = new Action(calculator.Report); calculator.Report(); action();//action.Invoke(): Func<int, int, int> func = new Func<int, int, int>(calculator.Add); Func<int, int, int> func1 = new Func<int, int, int>(calculator.Sub); int x = 100; int y = 200; int z = 0; z = func.Invoke(x, y); Console.WriteLine(z); z = func1.Invoke(x, y); Console.WriteLine(z); Type t = typeof(Action); Console.WriteLine(t.IsClass); Console.ReadLine(); } } class Calculator { public void Report() { Console.WriteLine("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; } }
(2) 委托的声明(自定义委托)
- 委托是一种类,类是数据类型,要在命名空间里声明;注意声明委托的位置,防止写出嵌套类型
- 委托所封装的方法必须类型兼容,返回值的数据类型一致,参数列表在个数和数据类型上一致,参数名不需要一样
public delegate int Calc(int x, int y); class Program { static void Main(string[] args) { Calculator calculator = new Calculator(); Calc calc1 = new Calc(calculator.Add); Calc calc2 = new Calc(calculator.Sub); int x = 100; int y = 200; int z = 0; z = calc1.Invoke(x, y); Console.WriteLine(z); z = calc2.Invoke(x, y); Console.WriteLine(z); Console.ReadLine(); } }
(3) 委托把方法当作参数传给另一个方法
- 模板方法:你写了一个方法,通过传进来的委托参数,借用指定的外部方法来产生结果;相当于填空题,常位于代码中部,委托有返回值;
- 回调方法callback:
- 回调指我给了你名片,你需要我就会打电话,不需要就不会。回调关系指某个方法可以调用,也可以不调用,可以动态选择后续被调用的方法(不止我一个人的名片,有很多人给了名片,从里面选需要的);
- 委托类型的参数传进主调方法里去,委托类型的参数封装了回调方法;主调函数会根据自己的逻辑决定是不是调用;主调函数会在主要逻辑执行完了后决定要不要调用,来执行后续工作,所以常位于代码尾部,委托无返回值,相当于流水线;
class Program { static void Main(string[] args) { ProductFactory productfactory = new ProductFactory(); WrapFactory wrapFactory = new WrapFactory(); Func<Product> func1 = new Func<Product>(productfactory.MakePizza); Func<Product> func2 = new Func<Product>(productfactory.MakeCar); Logger logger = new Logger(); Action<Product> log = new Action<Product>(logger.Log); Box box1 = wrapFactory.WrapProduct(func1, log); Console.WriteLine(box1.Product.Name); Box box2 = wrapFactory.WrapProduct(func2, log); 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 { public Box WrapProduct(Func<Product> getProduct, Action<Product> logCallback) { Box box = new Box(); Product product = getProduct.Invoke(); if (product.Price >=50) { logCallback(product); } box.Product = product; return box; } } class ProductFactory { public Product MakePizza() { Product product = new Product(); product.Name = "Pizza"; product.Price = 12; return product; } public Product MakeCar() { Product product = new Product(); product.Name = "Car"; product.Price = 100; return product; } }
(4) 多播委托
class Program { static void Main(string[] args) { Student stu1 = new Student(){ID = 1, PenColor= ConsoleColor.Yellow}; Student stu2 = new Student(){ID = 2, PenColor= ConsoleColor.Green}; Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Red }; Action action1 = new Action(stu1.DoHomework); Action action2 = new Action(stu2.DoHomework); Action action3 = new Action(stu3.DoHomework); //action1.Invoke(); //action2.Invoke(); //action3.Invoke(); action1 += action2; action1 += action3; action1.Invoke(); Console.ReadLine(); } } 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 {0} do homework {1} hours", this.ID, i); Thread.Sleep(1000); } } }
(5) 隐式异步调用
- 同步:你做完了我在你的基础上接着做;异步:咱俩同时做,相当于同步进行;
- 同步调用时在单线程里进行串行的调用,异步调用使用多线程进行并行调用;
- 直接同步调用:使用方法名;间接同步调用:使用单播或者多播委托的invoke方法;隐式异步调用:使用委托的beginInvoke;显示异步调用:使用Thread或task;
static void Main(string[] args) { Student stu1 = new Student(){ID = 1, PenColor= ConsoleColor.Yellow}; Student stu2 = new Student(){ID = 2, PenColor= ConsoleColor.Green}; Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Red }; //stu1.DoHomework(); //stu2.DoHomework(); //stu3.DoHomework();//同步 Action action1 = new Action(stu1.DoHomework); Action action2 = new Action(stu2.DoHomework); Action action3 = new Action(stu3.DoHomework); //action1.BeginInvoke(null, null); //action2.BeginInvoke(null, null); //action3.BeginInvoke(null, null);//隐式异步调用,自动生成分支线程,在分支线程里调用方法; //显示异步调用1 //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(); //显示异步调用2 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(); for (int i = 0; i < 10; i++) { Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("main thread {0}", i); Thread.Sleep(1000); } Console.ReadLine(); }
- 可以使用接口取代一些对委托的使用
13 事件
(1) 事件
- event,能够发生的什么事件,能给发生当主语的是事件
- 事件是一种使对象或者类型能够提供通知的成员,即使对象或类具备通知能力的成员;经由事件发送出来的与事件本身相关是数据称为事件参数(可选);根据通知和事件参数来采取行动的行为称为响应事件,即事件处理器。
- 使用:用于对象或类间的动作协调与信息传递(消息推送)
- 事件模型的两个5:
- 发生->响应的5个部分:闹钟响了你起床,隐含订阅关系
- 发生->响应的5个动作:我有一个事件->一个人或一群人关心我的这个事件->我的这个事件发生了->关心时间的人会被依次通知到->悲痛知道的根据事件参数对事件进行响应;
(2) 事件应用
- 事件模型:事件拥有者(event source,对象, 是object sender), 是拥有者的内部逻辑触发的;事件成员(event,成员);事件响应者(event subscriber);事件处理器(event hanlder)本质是一种回调方法;事件订阅(把事件处理器与事件关联在一起,本质上是一种以委托类型为基础的约定;
(3) 事件的声明
- 完整声明(代码注释部分);简略声明(字段式声明,field-like)
class Program { static void Main(string[] args) { Customer customer = new Customer(); Waiter waiter = new Waiter(); customer.Order += new OrderEventHandler(waiter.Action); customer.Action(); customer.PayBill(); Console.ReadLine(); } } public delegate void OrderEventHandler(Customer customer, OrderEventArgs e) ; public class Customer { //private OrderEventHandler orderEventHandler;//委托字段,用来存储或引用事件处理器 //public event OrderEventHandler Order //{ // add // { // this.orderEventHandler += value; // } // remove // { // this.orderEventHandler -= value; // } //} public event OrderEventHandler Order; public double Bill { get; set; } public void PayBill() { Console.WriteLine("p will pay the bill ${0}", this.Bill); } public void WalkerIn() { Console.WriteLine("walker into the room"); } public void SitDown() { Console.WriteLine("Sit down."); } public void Think() { Console.WriteLine("think"); Thread.Sleep(1000); if (this.Order != null)//this.orderEventHandler != null { OrderEventArgs e = new OrderEventArgs(); e.DishName = "chicken"; e.Size = "large"; this.Order.Invoke(this, e);//this.orderEventHandler.Invoke(this, e); } } public void Action() { Console.ReadLine(); this.WalkerIn(); this.SitDown(); this.Think(); } } public class OrderEventArgs:EventArgs//凡是用来传递事件数据的类都从EventArgs派生而来 { public string DishName { get; set; } public string Size { get; set; } } public class Waiter { public void Action(Customer customer, OrderEventArgs e) { Console.WriteLine("I will server you the dish {0}", e.DishName); double price = 10; switch (e.Size) { case "small": price = price * 0.5; break; case "large": price = price * 1.5; break; default: break; } customer.Bill += price; } }
- 事件的本质是委托字段的一个包装器。包装器对委托字段的访问起限制作用;封装的一个重要功能就是隐藏;事件对外界隐藏了委托实例的大部分功能,仅仅对外暴露添加/移除事件功能;
- 用于声明事件的委托类型的命名约定
- 用于声明Foo事件的委托,一般命名为FooEventHandler,除非是一个非常通用的事件约束:厂商定义的EventHandler;
- FooEventHandler的委托参数一般有两个,第一个是object类型,sender,是事件的拥有者,事件的source; 第二个是EventArgs类的派生类,名字一般为FooEventArgs,参数为e, 是事件参数;可以把委托的参数列表看作是事件发生后发送给事件响应者的事件消息;
- 触发Foo事件的方法一般命名为OnFoo,即因何而起事出有因;访问级别为protected;上面代码Think()思考还触发事件,一个方法两件事情,违反sing-responsibility原则;改为2
Main() { customer.Order += new EventHandler(waiter.Action); } public class Customer { public event EventHandler Order; public double Bill { get; set; } } public void Action(object sender, EventArgs e) { Customer customer = sender as Customer; OrderEventArgs orderInfo = e as OrderEventArgs; Console.WriteLine("I will server you the dish {0}", orderInfo.DishName); double price = 10; switch (orderInfo.Size) { case "small": price = price * 0.5; break; case "large": price = price * 1.5; break; default: break; } customer.Bill += price; }
public void Think() { Console.WriteLine("think"); Thread.Sleep(1000); this.OnOrder("chicken", "large"); } protected void OnOrder(string dishName, string size) { if (this.Order != null)//this.orderEventHandler != null { OrderEventArgs e = new OrderEventArgs(); e.DishName = dishName; e.Size = size; this.Order.Invoke(this, e); } }
- 事件本身,命名是带有时态的动词或动词短语;
(4) 事件与委托的关系
- 事件不是以特殊方式声明的委托;事件本质是加装在委托字段上的一个蒙版mask,是起掩蔽作用的包装器,这个阻挡非法擦偶哦的蒙版绝不是委托字段本身;
- 为何用委托:source角度是为了表明source能对外传递哪些消息;subscriber角度看,它是一种约定,为了约束能使用什么样签名的方法来处理事件;委托实例存储事件处理器;
14 类
(1) 类的声明
- 一个项目就是一个程序集。一个程序集可以体现为一个dll文件,或者exe文件。 internal限定的是只有在同一程序集中可访问, public可以跨程序集;
- 是一个is: 一个子类的实例在语义上讲也是父类的实例,一个派生类的实例在语义上讲也是基类的实例;Car is Vehicle;可以用父类的引用变量引用子类的实例;
- sealed封闭类,不能当基类了;一个类最多有一个基类(继承、派生),可实现多个基接口(实现);子类的访问级别不能超过父类的范围;
(2) 类的继承
- 派生类是在基类已有的成员基础上,对基类进行的横向和纵向的拓展;base只能访问上一级;
- 父类的实例构造器是不能被继承的。
class Vehicle { public Vehicle(string owner) { this.Owner = owner; } public string Owner { get; set; } } class Car:Vehicle { public Car(string owner) : base(owner) { this.Owner = owner;//可省略 } }
- private限制访问在定义的类体里,是默认的访问级别,保证数据的安全性;protected限制访问在继承链上;internal protected放在一起用是或的关系;
- 重写override ,父类须加virtual,就是子类更新父类的成员版本,子类里只存在一个最新版本;重写的发生条件:函数成员、可见、签名一致(函数名和参数,不包括返回值);
(3) 多态:基于重写机制,函数成员的具体行为由对象决定,C#语言的变量和对象都是有类型的,所以会有代差;
15 接口
(1) 接口和抽象类
- 抽象类指的是函数成员没有被完全实现的类,类里有至少一个没有被实现的类;abstract修饰,不能是private;作为基类,让别人派生;或声明变量去引用子类实例;
- 开闭原则,稳定的成员封装,有可能改变的声明为抽象成员,让子类去实现;
- 具体类-》抽象类-》接口,越来越抽象,内部实现的东西越来越少;方法体是方法的实现,字段实现了类怎么存储数据;
- 抽象类是未完全实现逻辑的类,可以有字段和非public成员,它们代表了具体逻辑;抽象类为复用而生,专门作为基类而使用,也具有解耦功能;
- 接口是完全未实现逻辑的类,纯虚类,成员全部public,不能实例化只能用来声明变量,引用具体类的实例;
alt+鼠标左键:选中多行同时编辑;Ctrl+E,F ----格式化选中的代码 ;Ctrl+E,D ----格式化全部代码
(2) 接口:为松耦合而生