C# 学习笔记
一.类与命名空间
类与命名空间都是一个抽象的概念。
个人的理解,命名空间相当于是很多类的集合,要想使用类中的特定功能,需要命名空间的调用。using System;
类中包括三大成员:属性,方法,事件。通过使用类中的相关内容,对程序进行实现。(不同类对于属性,方法和事件的侧重不同)
命名空间,类的引用有黑盒引用和白盒引用。
二.对象与实例化
对象:一般是称现实生活中所研究的物体。
类,中有很多的属性,这是抽象的概念。而类要实例化,并且对他附加属性。不能说飞机怎样怎样,但是一个飞机可以怎样怎样,这是我们可以实现的。
` Form myForm =new Form();` `myForm.Text ="My Form";` • myForm.ShouDialog();`
三.C#语言的基本元素
-
关键字
-
操作符
-
标识符
-
标点符号
-
文本
-
空白与注释
3.0F
:单精度浮点型;
4.0D
:双精度类型;
long
L
64比特位; var
自动获取类型。
变量 与方法(算法/函数)
四.类型在C#语言中的作用
-
一个C#类型中包含的信息有:
-
存储此类型的所需内存大小
-
此类型的值表示的最大,最小范围
-
此类型所包含的成员
-
此类型的基派衍生
-
此类型变量分配在内存的位置 Stack 与 Heap
-
运算
-
C#是强制类型语言,dynamic 兼容了动态类型。
五.C#语言的数据类型系统
-
C#语言的类型系统
-
引用类型:类,接口,委托
object string(1.太常用的数据类型,变为了关键字 2.最基本的构成其他的)
class interface delegate
-
值类型:结构体,枚举
bool byte char decimal double float int long sbyte short uint ulong ushort
(1.太常用的数据类型,变为了关键字 2.最基本的构成其他的)
struct enum
-
true false void null var dynamic
-
六.C#变量,对象与内存
-
变量是储存数据的
-
var 隐式变量,编译器自己推断是什么变量。
-
变量=以变量名所对应的内存地址为起点,以其数据类型所要求的存储空间为长度的一块内存区域。
-
变量表示了存储位置,并且每个变量都有一个类型,以决定什么样的值能够存入变量
-
静态变量,实例变量(字段),数组元素,值参数,引用参数,输出形参,局部变量, 狭义上,变量指的是局部变量。
-
-
-
值类型变量,值类型没有实例,所谓的”实例“与变量合而为一。(不用new)
-
引用类型的变量与实例
-
引用类型变量与实例关系:引用类型变量里存储的数据是对象的内存地址。
-
访问对象的属性,是在堆上开辟空间。
-
-
局部变量在stack上分配内存
-
变量默认值为0;(stack上未赋值的局部变量会报错)
-
装箱与拆箱
-
装箱是:把栈上的数据存储到堆上,而拆箱:是把堆上的数据移动回栈上。
-
装箱与拆箱都会消耗性能。
-
七.方法(函数)
-
方法的前身是C/C++语言中的函数(function)
-
方法是面向对象范畴的概念,在非面向对象语言中仍然成为函数。
-
可以理解为,把函数封装成立类的一个成员属性,这个属性理解为方法。
-
-
永远都是类(结构体的)成员
-
C#语言中函数不可能独立于类(结构体)之外
-
只有作为类的成员才能被称为方法
-
C++中是可以称为”全局函数“
-
-
类与结构体最基本的成员有:字段与方法,本质还是算法与数据。方法表示的是可以做什么事情
-
需要的原因是:1.隐藏复杂的逻辑 2.把大算法变成小算法 3.复用
-
-
命名的规范
-
大小写的规范,单词全部首字母大写
-
用动词或者动词短语作为名字
-
-
static修饰需要对类进行调用,而不是实例化调用
-
C#是强类型语言,值与变量的类型一定要匹配,不然编译器会报错。
八.Overload
-
构造器:是类的成员之一(狭义的构造器是“实例构造器”);
构造器可以要求强制初始化;
-
声明带有重载的方法
-
方法签名由方法的名称,类型形参的个数和它的每一个形参(从左到右的顺序)的类型和种类(值,引用或输出)组成,方法签名不包含返回类型。
-
实例构造函数前面由它的每一个形参(从左到右的顺序)类型和种类(值,引用或输出)组成。
-
重载决策(调用哪个重载)选择。
-
九.操作符
-
本质:1.操作符的本质是函数(算法)的“简计法” 2.操作符要与数据类型关联
-
default操作符:1.结构体类型,将内存中的值全部刷为0,并且返回0;
2.枚举类型,枚举中变量会有其对应的值,返回为0的
3.引用类型,也是全刷为0,返回的是null
-
new:1.在内存当中创造类的实例,并且立刻调用实例构造器(也可以调用初始化器)
2.有赋值操作符,会把实例的地址赋值给变量
-
checked 与unchecked 是操作符但是常用的是上下文
uint x=uint.MaxValue; Console.WriteLine(x); string binStr=Convert.ToString(x); Console.WriteLine(binStr); unchecked(检查与不检查一个值在内存中是否溢出)上下文 { try { uint y = x + 1; Console.WriteLine(y); } catch (OverflowException ex) { Console.WriteLine("There "); } }
-
delegate 操作符被更加简单的 Lambda 表达式取代了。与匿名函数的创建有关。
-
is操作符是判断是否为某类的实例化,要求需要符合逻辑
-
as 操作符,是是否像什么一样,一样返回地址,不一样返回null
-
? 与??操作符
int? x = null; int y = x ?? 10; Console.WriteLine(y);
十.类型转换
-
隐式类型转换
-
不丢失精度的转换(int->long)
-
子类向父类转,类是子类从父类继承,子类可以继承父类的属性,但是父类可以容纳子类的地址,但是属性不可为子类,而为父类的属性。
-
装箱
-
-
显式类型转换
-
可能会丢失精度(甚至产生错误)转换
-
拆箱
-
Convert类的使用
-
ToString方法(object 最基础的属性),以及Parse/TryParse方法
-
double.Parse("123");这个字符串必须正确,否则没有办法转换
-
-
-
自定义的类型转换操作符
-
类型转换也相当于是函数
namespace studyC_ { internal class Program { static void Main(string[] args) { Stone stone= new Stone(); stone.Age = 5000; Monkey wukongSun = (Monkey)stone; Console.WriteLine(wukongSun.Age); } } class Stone { public int Age; public static explicit operator Monkey(Stone stone) { Monkey m= new Monkey(); m.Age = stone.Age/500; return m; } } class Monkey { public int Age; } }
十一.语句详解
-
语句的分类
-
标签语句,嵌入式语句,声明语句
-
-
try 语句 try catch final语句
-
try
{
}
catch(ArgumentNullException/FormatException/OverflowExceptin)
{
}
-
catch 的问题还可以throw ,throw 可以把问题抛给调用的实例,然后再try,catch。
-
throw的使用很是灵活。
-
-
foreach语句,遍历枚举一个集合元素。并对该集合中每个元素执行一次相关的嵌入语句。
-
可以被遍历的集合:数组和泛型的实例。所有数组类型都是Array这个基类。 Array的一个接口,I开头 IEnumerable 这个接口只有一个方法,GetEnumerator()迭代器。
-
迭代器,using System.Collections;
储存迭代器容器IEnumerable它有3个object,Current 是当前,
Reset( )是拨回去,MoverNext( )是下一个。
-
List<int> list = new List<int>() { 1,2,3,4,5,6,7};
foreach (var current in list)
{
Console.WriteLine(current);
}
-
十二.字段,属性,索引,常量(类和结构体所具有的成员)
-
字段
-
字段(field)与对象或类型(类与结构体)关联的变量
与对象关联的字段叫做”实例字段“,与类型关联的字段”静态字段“,由static修饰,调用的时候也是直接调用。
-
字段的修饰,只读字段可以初始化(构造器初始化),但是不能够赋值。(readonly),只读字段的初始化是要借用构造器,但决定不能赋值。(静态与非静态一样)
-
-
属性
-
set 与 get 使用 set 是设置值的,get是获取值的
-
propfull 可以快捷设置get set
-
-
属性是一种用于访问对象或类型的特征的成员,特征反映了状态
-
属性是字段的自然扩展
-
field更偏向于实例对象在内存中的布局,property更偏向于反映现实世界的特征
-
对外:暴露数据,数据可以存储在字段里,也可以是动态计算出来的
-
对内:保护字段不被非法值”污染“
-
-
属性由Get/Set方法进化而来,是一种语法糖
-
实例属性,与静态属性(static)
-
只读属性只有getter,没有set,怎样赋值都会报错。
-
private set { },的话,只能在类里面进行赋值,在外面没有办法访问。
-
要用属性暴露数据,而字段都是private或protected
-
-
索引器
-
它可以使对象能够与数组相同的方式进行索引。index
-
-
常量
-
为了提高程序可读性与执行效率——常量
-
为了防止对象值被改变——只读字段
-
向外暴露不允许修改的数据——只读属性
-
当希望成为常量的值其类型不能被常量声明接时——静态只读字段。
-
十三.参数
-
传值参数
-
传值参数与值类型 :创建变量的副本 对值参数操作不会影响变量的值
-
传值参数与引用类型,要创建新对象
-
值参数创建变量副本,对值参数的操作永远不影响变量值
只操作对象,不创建新对象
-
对象还是那个对象,但是对象中的值已经改变了
-
-
-
引用参数
-
引用参数与值类型:不创建变量副本,ref修饰符显式指出----此方法的副作用是改变实际参数的值
-
引用参数与引用类型,创建新对象
-
引用参数不创建变量副本,ref修饰符显式指出---改变实参值
不创建新对象只改变值
-
与传值参数效果相同,但是机理不同
-
-
-
输出参数
-
输出参数与值类型:输出参数并不创建变量的副本,方法体内必需有对输出变量的赋值操作,使用out修饰符显式指出----此方法的副作用是通过参数向外输出值,ref是为了改变,out是为了输出。
-
输出参数与引用类型:输出参数不创建副本,方法体内必须要有对输出变量赋值的操作,使用out修饰符显式指出----此方法的副作用是通过参数向外输出值,ref是为了改变,out是为了输出。
Console.WriteLine("please write a number"); string arg1 =Console.ReadLine(); double x = 0; bool b1=double.TryParse(arg1, out x); if(b1 == false ) { Console.WriteLine("your enter has error"); return; }
-
-
数组参数
-
必须是形参列表中最后一个,由params修饰
-
str.Splist('.' , ',');
-
-
具名参数
-
参数的位置不再受约束
-
-
可选参数
-
参数因为有默认值而变得”可选“
-
-
this 参数
-
公用,静态的,public static所修饰
-
必须是形参列表中第一个,由this修饰
-
必须由一个静态类,一般类名为SomeTypeExtension,来同意收纳扩展方法
-
LINQ方法
-
-
总结
传值参数:参数的默认传递方法
输出参数:用于除返回值外还需要输出的场景
引用参数:用于需要修改实际参数值的场景
数据参数:用于简化方法的调用
具名参数:提高可读性
可选参数:参数拥有默认值
this参数:为目标数据类型”追加“方法
十四.委托和声明
-
委托(delegate)是函数指针的”升级版“
`Calculatar calculatar = new Calculatar();` `calculatar.Report();` `Action action = new Action(calculatar.Report);` `action.Invoke();` action();
-
一切皆是地址
-
变量(数据)是以某个地址为起点的一段内存中所储存的值
-
函数(算法)是以某个地址为起点的一段内存中所存储的一组机器语言指令
-
-
直接调用与间接调用
-
直接调用:通过函数名来调用函数,CPU通过函数名直接获得函数所在地址并开始执行--->返回
-
间接调用:通过函数指针调用函数,CPU通过读取指针存储的值获得函数所在地址并开始执行--->返回
-
-
Java中没有与委托相对应的功能实体
-
委托的简单使用
-
Action委托
-
Func委托
-
-
委托是一种类(总的命名空间里面),类是数据类型所有委托也是一种数据类型
-
它的声明方式与一般的类不同,主要是为了照顾可读性和C/C++传统
delegate
type
Calc
(type x,
type` y);
委托的高级使用
-
多播(multicast)委托
-
隐式异步调用
-
同步与异步
-
同步:你做完了我(在你的基础上)接着做
-
异步:咋们两个同时做(中文意思是同步进行)
-
-
同步调用与异步调用的对比
-
每一个运行的程序都是一个进程(process)
-
每个进程可以有一个或者多个线程(thread)
-
同步调用是在同一个线程内
-
异步调用的底层机制是多线程
-
串行= =同步= =单线程。并行= =异步= =多线程
-
-
隐式多线程 与 显式多线程
-
直接同步调用:使用方法名
-
间接同步调用:使用单播/多播委托的Invoke方法
-
隐式异步调用:使用委托的BenginInvoke
-
显示异步调用:使用Thread或Task
-
应当适当地使用接口(interface)取代一些对委托的使用
-
java完全用接口取代了委托的功能,即Java没有与C#中委托相对应的功能实体
-
十五.事件
初步了解事件
-
定义:”能够发生什么事情“
-
角色:使对象或类具备通知能力的成员
-
An event is a member that enables an object or class to provide notifications
-
-
使用:事件模型
-
1.关心的事情 2.有东西关心这个事件 3.事件发生了
4.这个关心的东西被依次通知到了 5.处理事件
-
-
注意:
-
事件多用于桌面,手机等开发客户端编程
-
各种语言实现方法并不相同
-
Java接口来实现
-
MVC,MVP,MVVM是事件更加高级,更有效的”玩法“
-
事件的应用
-
派生(继承)与扩展(extends)
-
事件模型五部分
-
event source
-
event
-
eventsubsciber
-
eventhandler
-
order
-
-
注意
-
事件处理器是方法成员
-
挂接事件处理器的时候,可以使用委托实例,也可以直接使用方法名,”语法糖“
-
事件处理器对事件的订阅不是随意的,匹配与否由声明事件时所使用的委托类型来检测
-
事件可以同步调用也可以异步调用
-
事件的声明
-
事件的声明
-
完整声明
-
简略声明(字段式声明,field-like)
-
-
原因:谨防”借刀杀人“
-
事件的本质是委托字段的一个包装器
-
这个包装器对委托字段的访问起限制作用,相当于”蒙版“
-
封装(encapsulation)的一个重要功能就是隐藏
-
事件对外界隐藏了委托实例的大部分功能,仅暴露添加/移除事件处理器的功能
-
添加/移除事件处理器的时候可以直接使用方法名,这是委托实例所不具备的功能
-
-
用于声明事件的委托类型的命名约定
-
用于声明Foo事件的委托,一般命名为FooEvebtHandler(除非是一个非常通用的事件约束)
-
FooEventHandler委托的参数一般有两个
-
第一个是object类型,名字为sender,实际上就是事件的拥有者,事件的source
-
第二个是EventArgs类的派生类,类名一般为FooEventArgs,参数名为e。事件参数
-
虽然没有官方说法,但是我们可以把委托的参数列表看成是事件发生后发送给事件响应者的”事件消息“
-
-
触发Foo事件的方法一般命名为OnFoo,即”因何引发“,”事出有因“
-
访问级别为protected,不能为public,否则又是”借刀杀人“了
-
-
-
事件的命名约定
-
带有时态的动词或者动词短语
-
事件拥有者“正在做”什么事,用进行时;事件拥有者“做完了”什么事情,用完成时
-
-
事件与委托的关系
-
事件声明的时候使用了委托类型,简化声明造成事件看上去像一个委托的字段(实例),而event关键字更像是一个修饰符-----这就是错觉来源。
-
事件的本质是加装在委托字段上的一个“蒙版”,是个起掩蔽作用的包装器。这个用于阻挡非法操作的“蒙版”绝不是委托字段本身
-
为什么要使用委托类型声明事件
-
source角度,source能对外传递那些消息
-
subscriber的角度,一种约定,为了约束能够使用什么样的签名方法来处理,(响应事件)
-
委托类型的实例用于储存事件处理器
-
-
对比事件与属性
-
属性不是字段——很多时候属性是字段的包装器,这个包装器用来保护字段不被滥用
-
事件不是委托字段——它是委托字段的包装器,这个包装器用来保护委托字段不被滥用
-
包装器永远都不可能是被包装的东西
-
-
十六.类(声明即定义)
-
是一种数据结构
-
一种数据类型
-
代表现实世界中的“种类”
析构器(析构函数)
-
~Student(){ }
-
只要创造了实例,析构器就会被调用,释放一个对象的
类的继承
-
类在功能上的扩展,只能有一个基类,但是可以实现多个基接口
-
private < protected < internal < public
-
private 仅仅只能在类内部调用,最低级的访问级别
-
protected 自己与自己的子类可以调用
-
internal 仅为同项目,是单独的项目。
-
public 最高级的访问级别,所有成员对所有人都是可以取用
-
-
sealed类修饰说明该类不能被继承或者重写。
-
成员的继承与访问
-
派生类对继承成员的访问
-
派生类对基类成员的访问
-
构造器不可继承,如果基类的构造器初始化了,衍生类的构造器必须初始化
class Vehicle { public string Owner { get; set; } public Vehicle(string owner) { this.Owner = owner; } } class Car:Vehicle { public Car(string name) : base(name) { } public void Report() { Console.WriteLine(Owner); } }
-
-
“横向扩展” (成员越来越多)
多态----类的纵向继承
-
行为不变,版本升高
-
重写与隐藏:函数成员,可见,签名一致
-
多态基于重写机制(virtual---->override)
class Vehicle { private int _speed; public virtual int Speed { get { return _speed; } set { _speed = value; } } public virtual void Run() { Console.WriteLine("I am running"); _speed = 100; } } class Car : Vehicle { private int _rpm; public override void Run() { Console.WriteLine("Car is running"); _rpm = 10000; } public override int Speed { get { return _rpm/100; } set { _rpm = value * 100; } } }
十七.接口和抽象类
-
接口和抽象类都是“软件工程产物”
-
具体类——>抽象类——>接口:越来越抽象,内部实现的东西越来越少
-
抽象类是未完全实现逻辑的类(可以有字段和非public成员,它们代表了“具体逻辑”)
-
抽象类为复用而生:专门作为基类来使用,也有解耦功能
static int Sum(int[] nums) { int sum = 0; foreach(var n in nums) sum += n; return sum; } static double Avg(int[] nums) { int sum = 0;double count = 0; foreach(var n in nums) { sum += n;count++; } return sum/count; } static double Avg2(ArrayList nums) { int sum = 0; double count = 0; foreach(var n in nums) { sum =sum+(int)n; count++; } return sum/count; }
-
封装确定的,开放不确定的,推迟到合适的子类中去实现
-
接口是完全未实现逻辑的“类”(纯虚类,只有函数成员,成员全部public)
-
接口为解耦而生:“高内聚,低耦合”,方便单元测试
-
接口是一个“协约”
-
他们不能够实例化,只能用来声明变量,引用具体类的实例
interface VehicleBase { void Stop(); void Run(); void Fill(); } abstract class Vehicle : VehicleBase { public void Stop() { Console.WriteLine("Stopped!"); } public void Fill() { Console.WriteLine("pAY AND FILL"); } 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("T is running"); } }
接口与单元测试
-
接口的产生:自底向上(重构),自顶向下(设计)
-
C#中接口的实现(隐式,显式,多接口)
-
原则:倚赖反转原则,接口隔离,开/闭原则
-
接口隔离原则,一个类只做相关一件事(接口存在两个以上的接口使用,包含了太多功能,有一部分功能实现不了)
-
十八.反射与注入
-
反射:以不变应万变(更松的耦合),反射机制,封装好的反射,逻辑并不确定
-
反射与接口的结合
-
反射与特性的结合
-
依赖注入
ITank tank = new HeavyTank(); var t=tank.GetType(); object o=Activator.CreateInstance(t); MethodInfo FireMi = t.GetMethod("Fire"); MethodInfo RunMi =t.GetMethod("Run"); FireMi.Invoke(o, null); RunMi.Invoke(o, null);
十九.泛型与partial类
-
泛型
-
为了避免成员膨胀或者类型膨胀
-
正交性:泛型类型(类/接口/委托......) 泛型成员(属性/方法/字段......)
class Box<T> { public T Cargo { get; set; } }
-
类型方法的参数推断
static T[] Zip<T>(T[] A, T[] B) { T[] Z = new T[A.Length + B.Length]; int a = 0; int b = 0; int c = 0; do { if (a < A.Length) Z[c++] = A[a++]; if (b < B.Length) Z[c++] = B[b++]; } while (a < A.Length || b < B.Length); return Z; }
-
-
partial类
-
减少类的派生
-
分成多部分进行编写,可以用不同语言编写
-
二十.lambda表达式
-
lambda表达式,用来表达匿名的方法,inline方法
Func<int, int, int> func = (a, b) => a + b;
int ret = func(100, 200);
Console.WriteLine(ret);
Func<double, double, double> func1 = (a, b) => a * b;
double ret2 = func1(2.2, 7);
Console.WriteLine(ret2);