C#学习笔记(二)
表达式
编程语言的表达式:用于求值的表达实体,最小的算法元素
C#对表达式的定义: (一个或多个操作数)和(0个或多个操作符组成)
获取类型名称:xxx.GetType().FullName
语句
定义:最小的独立元素,表达式可以组成语句,语句是高级语言才有的,低级语言只有指令。
高级语言中的表达式对应低级语言中的指令,语句等价于一个或一组有明显逻辑关联的指令。
编程:使用语句编写程序
变量的作用域:
字段
什么是字段:
- 一种表示与对象或类型(类或结构体)关联的变量
- 字段是类型的成员(声明在方法里的是局部变量,不叫字段),旧称成员变量
- 与对象关联的字段也称为“实例字段”
- 与类型关联的字段称为静态字段,由static修饰
字段的声明:
- 一定是名词
字段修饰符:
- new
- public:(可以修饰类也可以修饰成员)公共的,最高的访问级别,访问没有限制,命名大写字母开头
- protected:(只修饰成员)受保护的, 只有在当前类内部及所有的子类内部可以访问。基类中访问无限制,子类中直接访问或通过子类实例访问。
- private:(只修饰成员)私有的,最低的访问级别,被private访问修饰符修饰的成员只有在当前类的内部可以访问,其他地方一律不得访问,命名小写字母开头
- static
- readonly
- 实例只读字段:只能在创建实例的时候可以初始化
- 静态只读字段:静态构造器中初始化
- volatile
this代表当前实例
字段的初始值:
- 无显示初始化时,字段获得其类型的默认值,所以字段“永远都不会被初始化”
- 静态字段初始化时期:类型被加载(load)时,静态构造器,加载这个类型的时候
// 静态构造器 Class Brush { public static readonly int a; static Brush() { Brush.a = 10; } }
- 实例字段初始化时机:对象创建时
属性
什么是属性
- 属性(property)是一种用于访问对象或类型特征的成员,为C#所特有
- 属性是字段的自然扩展
- 从命名上看,field更偏向于实例对象在内存中的布局, property更偏向于反应现实世界对象的特征
public int Age { get { return this.age; } set // value是set中才有的关键字,代表用户传进来的值,可以在set中进行值检验 { this.age = value; } }
// 使用
class.Age - 对外:暴露数据,数据是可以存储在字段里的,也可以是动态计算出来的
- 对内:保护字段不被非法值污染
- 从命名上看,field更偏向于实例对象在内存中的布局, property更偏向于反应现实世界对象的特征
- 属性由Get/Set方法对进化而来
- 语法糖
属性的声明
- 完整声明:propfull 加两下tab(也可以通过vs的refactor工具)
class Student { private int myVar; // 被属性包装的字段的名字 public int MyProperty { get { return myVar; } set { myVar = value; } // 可以在这里加代码以保护逻辑 } }
-
简略声明:通过简略声明声明出来的属性,功能上和一个公有的字段是完全相同的,好处在于声明起来比较简单,带有这种属性的类就是为了传递数据用的
// prop 两下tab public int MyProperty {get; set;}
属性和字段的关系
- 一般情况下,它们都用于表示实体(对象或类型)的状态
- 属性大多情况下是字段的包装器(wrapper)
- 建议:永远使用属性(而不是字段)来暴露数据,即字段永远是private或protected
索引器
索引器(indexer)是这样一种成员:它使对象能够用与数组相同的方式(即使用下标)进行索引,一般用在集合里面
索引器的声明
indexer 两下tab
常量
const 修饰,隶属于类型而不是对象,没有实例常量,”实例常量“的角色由只读实例字段来担当。可以提高程序的可读性和执行效率。
参数
传值参数:声明时不带修饰符的形参是值形参
后续。。。。
委托
什么是委托
- 委托(delegate):是函数指针的升级版,java中没有与委托相对应的功能实体
- 实例:C/C++中的函数指针
// 定义一个名为Calc函数指针,它指向了有两个参数,且返回整型的函数,并把它定义为一种数据类型 typedef int(*Calc)(int a, int b); // 使用, Add,Sub是已定义好的函数 Calc func1 = &Add; Calc func2 = ⋐ z = func1(1, 2);
- 实例:C/C++中的函数指针
- 一切皆地址
- 变量(数据)是以某个地址为起点的一段内存中所储存的值
- 函数(算法)是以某个地址为起点的一段内存中所存储的一组机器语言指令
- 直接调用与间接调用
- 直接调用:通过函数名来调用函数,CPU通过函数名直接获得函数所在地址并开始执行,返回
- 间接调用:通过函数指针来调用函数,CPU通过读取函数指针存储的值获得函数所在地址并开始执行,返回
- 委托的简单使用
- Action委托:用于没有返回值的方法
// 声明 Action action = new Action(目标方法名); // 使用1 action.Invoke(); // 使用2 action();
- Func委托: 用于有返回值的方法
// 声明,可以没有参数 func<参数1类型,参数2类型, 返回值类型> 委托名= new Func<参数1类型,参数2类型,返回值类型>(方法名); // 使用1 委托名.Invoke(x, y); // 使用2 委托名(x, y);
- Action委托:用于没有返回值的方法
委托的声明(自定义委托)
- 委托是一种类(class),类是数据类型,所以委托也是一种数据类型
- 它的声明方式与一般类不同,主要是为了照顾可读性和C/C++传统
// 自定义委托 public delegate 返回值类型 委托类名(参数1类型 参数1名称, 参数2类型,参数2名称); // 自定义委托声明 委托类名 委托实例名 = new 委托类名(目标方法名); // 自定义委托使用1 委托实例名.Invoke(参数1,参数2); // 自定义委托使用2 委托实例名();
- 注意声明委托的位置
- 应该声明在名称空间体内,与其他的类处在同一级别
- 自定义的委托与所封装的方法必须”类型兼容“
- 返回值的类型抑制
- 参数列表在个数和数据类型上一致
委托的一般使用
- 实例:把委托当做方法的参数传给另一个方法,形成一种动态调用方法的结构
- 正确使用1:模板方法,”借用“指定的外部方法来产生结果:符合面向对象的开闭原则,即软件修改的时候,应该尽量用扩展进行变化,而不是通过修改已有的代码。
- 使代码复用reuse,通过委托来调用相关的方法,如果扩展的话,只需要写在委托中就行了,不需要修改原来的类(python装饰器功能类似)
- 把方法当做参数传递,达到动态调用的方式
正确使用2:回调(callback)方法,调用指定的外部方法
- 无论是模板方法还是回调方法,都是用委托类型的参数,封装一个外部方法,把这个方法传进方法的内部,再进行间接调用
注意:难精通 + 易使用 + 功能强大,一旦被 滥用后果很严重
- 正确使用1:模板方法,”借用“指定的外部方法来产生结果:符合面向对象的开闭原则,即软件修改的时候,应该尽量用扩展进行变化,而不是通过修改已有的代码。
- 缺点1:这是一种方法级别的紧耦合,现实工作中要慎用
- 缺点2:可读性下降,debug难度增加
- 缺点3:把委托回调,异步调用和多线程纠缠在一起,会让代码变得难以阅读和维护
- 缺点4:委托使用不当有可能造成内存泄漏和程序性能下降
委托的高级使用
- 单播委托:一个委托封装一个方法的使用形式。
- 多播(multicast)委托:一个委托内部封装了不止一个方法,也是同步调用。action1 += action2
- 隐式异步调用
- 同步与异步简介
- 中英文有语言差异
- 同步:A先做完B再接着A做
- 异步:A和B同时做,相当于汉语中的同步进行
- 同步调用和异步调用对比
- 每一个运行的程序是一个进程(process)
- 每个进程可以有一个或者多个线程
- 同步调用是在同一线程内
- 异步调用的底层机理是多线程
- 串行 == 同步 == 单线程, 并行 == 异步 == 多线程
- 隐式多线程 V.S. 显示多线程
- 直接同步调用:使用方法名
- 间接同步调用:使用单播/多播委托的Invoke方法
- 隐式异步调用:使用委托的BegingInvoke,action1.BeginInvoke(Callback, object) BeginInvoke方法会自动的生成一条分支线程,在这个线程中去执行方法。callback参数是执行完后回调的方法,不需要可以为null,另一个参数一般为null
- 显示异步调用:使用Thread或Task
- 同步与异步简介
- 应当适时地使用接口(interface)取代一些对委托的使用
- Java完全地使用接口取代了委托的功能
事件
初步了解事件
- 定义:Event,能够发生的什么事情
- 角色:使对象或类具备通知能力的成员
- 使用:用于对象或类间的动作协调与信息传递(消息推送)
- 原理:时间模型(event model)中的两个5
- ”发生 >响应“中的5个部分:闹钟响了人起床,闹钟,响,人,起床,暗含了人订阅了闹钟响了的事件,共5个
- ”发生 >响应“中的5个动作:
- 我有一个事件(事件参数)
- 一个或一群人关心(订阅)这个事件(事件的订阅者)
- 我的事件发生了
- 关心这个事件的人会收到通知
- 收到通知的人会对事件进行影响(处理事件)
- 提示
- 事件多用于GUI界面开发
- 各种编程语言对这个机制的实现方法不尽相同
- java中没有事件这种成员。Java用接口来实现事件
- MVC, MVP, MVVM等模式是事件模式的更高级,更有效的用法(事件模式牵扯到的元素太多,有5个)
- 日常开发的时候,使用已有事件的机会比较多,自己声明的机会比较少
事件的应用
- 事件模型的五个部分
- 事件的拥有者(event source , 对象)
- 事件成员(event, 成员, 事件本身,向订阅者发送通知 )
- 事件的响应者(event subscriber, 对象或类,拥有自己的事件处理器,订阅了事件的对象或类)
- 事件处理器(event handler,方法成员,回调方法),sender:事件的发送者
- 事件订阅——把事件处理器与事件关联在一起,本质上是一种以委托类型为基础的“约定”
- 事件模型五个部分的不同组合
- 事件的响应者去向事件的拥有者订阅事件,两个类(MVC模式的雏形)
using System; using System.Timers; namespace ConsoleApp2 { class Program { static void Main(string[] args) { Timer timer = new Timer(); // 事件的拥有者 timer.Interval = 1000; // 1000ms执行一次 Boy boy = new Boy(); // 事件的响应者 // 事件: timer.Elapsed timer.Elapsed += boy.Action; //事件 订阅(+=) 事件处理器 timer.Start(); Console.ReadLine(); } } class Boy { // 事件的处理器 internal void Action(object sender, ElapsedEventArgs e) { Console.WriteLine("Jump"); } } }
using System; using System.Windows.Forms; namespace netframework { class Program { static void Main(string[] args) { Form form = new Form(); // 事件拥有者 Controler controler = new Controler(form); // 事件响应者 form.ShowDialog(); } } class Controler { private Form form; public Controler(Form form) { if (form != null) { this.form = form; this.form.Click += this.FormClicked; // 事件 订阅 事件处理器 } } // 事件处理器 private void FormClicked(object sender, EventArgs e) { this.form.Text = DateTime.Now.ToString(); } } }
- 事件的拥有者同时也是事件的响应者 ,拥有者和响应者属于同一个类
using System; using System.Windows.Forms; namespace netframework { class Program { static void Main(string[] args) { MyForm form = new MyForm(); // 事件拥有者 事件响应者 form.Click += form.FormClicked;// 事件 订阅 事件处理器 form.ShowDialog(); } } class MyForm : Form { internal void FormClicked(object sender, EventArgs e) { this.Text = DateTime.Now.ToString(); } } }
- 事件的拥有者是事件的响应者的一个字段成员,事件的响应者用自己的一个方法订阅了自己成员的某个事件,也是windows平台上默认的事件订阅处理结构,意义最大
using System; using System.Windows.Forms; namespace netframework { class Program { static void Main(string[] args) { MyForm form = new MyForm(); // 事件响应者 form.ShowDialog(); } } class MyForm : Form { private Button button; // 事件拥有者 public MyForm() { this.button = new Button(); this.Controls.Add(this.button); this.button.Click += this.ButtonClicked; // 事件 订阅 事件处理器 } // 事件处理器 private void ButtonClicked(object sender, EventArgs e) { this.Text = DateTime.Now.ToString(); } } }
- 事件的响应者去向事件的拥有者订阅事件,两个类(MVC模式的雏形)
- 事件的本质是委托字段的一个包装器
- 这个包装器对委托字段的访问起限制作用
- 封装的一个重要功能就是隐藏
- 事件对外界隐藏了委托实例的大部分功能,仅暴露添加/移除事件处理器的功能
- 添加/移除事件处理器的时候可以直接使用方法名,这是委托实例所不具备的
- 用于声明事件的委托实例的命名约定
- 用于声明Foo事件的委托,一般命名为FooEventHandler(除非是一个非常通用的事件约束)
- FooEventHandle委托的参数一般由两个(由WIn32 API演化而来,历史悠久)
- 第一个是object类型,名字为sender,实际上就是时间的拥有者、事件的source
- 第二个是EventArgs类的派生类,类名一般为FooEventArgs,参数名为e。也就是前面讲的事件参数。
- 触发Foo事件的方法一般命名为OnFoo,即因何引发
- 访问级别为protected,不能public,不然又成了可以"借刀杀人"
- 事件的命名约定
- 带有时态的动词或者动词短语
- 事件拥有者"正在做"什么事情,用进行时; 事件拥有者做完了什么事情,用完成时
- 注意
- 时间处理器是方法成员
- 挂接处理器(使用+=挂接)的时候,可以使用委托实例,也可以直接使用方法名,这是个语法糖-
- 事件处理器对事件的订阅不是随意的,匹配与否由声明事件时所使用的委托类型来检测
- 事件可以同步调用也可以异步调用
事件的声明(自定义声明)
- 声明
- 完整声明
// 委托声明 public
- 简略声明
- 完整声明
快捷键:
完整声明属性:propfull 两下tab
简略声明属性:prop 两下tab
索引器声明:indexer 两下tab
visual studio中的图标: 小闪电--事件(一定条件下可以通知谁,通知别人),小方块---方法(能做什么,做事情), 小扳手--属性(处于什么状态,存储数据),类的最重要的三块。