[读书笔记]《C#本质论》读书笔记
【Note】局部变量名采用的是camel大小写形式,而且不包含下划线
【Note】隐式类型var
匿名类型的一个实例赋给一个隐式类型的变量:
var patent=
new { Title = "Bifocals",
YearOfPublication = "1784"};
【Note】可空修饰符
如 int? count = null;
【Note】默认情况下unchecked,即赋值溢出时采取截断,可以选择checked来引发异常
【Note】交错数组
声明一个交错数组: (内部数组都要实例化)
int[][] cells={
new int[]{1,0,2,0},
new int[]{1,2,0},
new int[]{1,2},
new int[]{1}
};
【Note】数组的Length属性问题
正常数组的Length是取得数组所有元素的总数
交错数组是取得内部包含的数组的数目
上面的cells.Length就等于4
int[,] array1 = new int[2, 3]; // 定义一个 2 X 3 的数组
int allLen = array1.Length; // 获取所有维度的元素数 6
int d1Len = array1.GetLength(0); // 获取第一个维度的元素数 2
int d2Len = array1.GetLength(1); // 获取第二个维度的元素数 3
【Note】一些数组的静态方法
string[] stringArray = new string[] {.....};
System.Array.Sort(stringArray ); //升序排序
System.Array.BinarySeach((stringArray ,"匹配内容") //该方法调用之前必须先Sort
System.Array.Reverse((stringArray) //将数组内的元素反序
System.Array.Clear(stringArray,0,stringArray.Lengh) //将数组内的元素置为默认值
【Note】数组的一些实例方法
Length
GetLength()
Rank //获取整个数组的维数
Clone()
【Note】数组的定义以及取值
int[,] xxx = new int[3,4];
int sss = xxx[0, 0];
int[][] fff = new int[3][]; //交错数组
*foreach遍历 交错数组是一个一个的数组 普通多维数组是一个一个的值
【Note】空接合运算符
例: fileName??"default.txt"
【Note】C#的命名空间必须显式导入,不能像java那样使用通配符 import javax.swing.*;
即C#不会导入任何嵌套的命名空间
【Note】函数参数
传入功能(传值) 传入传出功能(传引用ref) 传出(out)
【Note】参数数组
(1)要在方法声明的最后一个参数声明前面添加params,不能在非最后一个参数前面加
(2)最后个参数要声明为数组
实参可以逗号隔开,也可以以数组的形式
例: static void temp( params string[] par){} //声明方法
temp("sdfs", "sadasd"); //调用方法
【Note】函数声明的可选参数
例: static int function(string str1, string str2 = "str2Value")
* 默认值只能是常量
【Note】函数调用的命名参数
例: public void functions(string str1,string str2="str2Value,string str3="str3Value"") //函数声明
function(str1:"myValue" , str3:"myValue");//函数调用
【Note】属性支持为set和get添加访问修饰符
(该修饰符必须比该属性的修饰符 严格,比如属性是private,set和get就不能是public)
【Note】属性和方法调用不允许作为ref和out参数值使用
【Note】对象初始化器
Employee employee1= new Employee("Inigo","Montoya")
{Title="title" , Salary="Not enough"};
【Note】集合初始化器
List<Employee> employees = new List<Employee>()
{ new Employee("Inigo","Montoya"),
new Employee("Inigo","Montoya")
};
【Note】构造器初始化器
class myClass
{
public myClass(){ //其他代码 }
public myClass(string str):this(){ //其他代码 }
}
* 在构造函数内部是调用不了其他构造函数的,不像普通函数的重载
【Note】将匿名类型的实例赋给隐式类型的局部变量
var par = new { value1 = "par_value1", value2 = "par_value2" };
var par2 = new { par.value1, value2 = "par2_value2" };
System.Console.WriteLine(par2.value1);
最终输出par_value1
【Note】静态类
编译器会自动在CIL代码中将静态类标记为abstract和sealed。
* abstract说明不能实例化
* sealed说明不能被继承
* 仅包含静态成员
* 不能包含实例构造函数,但仍可声明静态构造函数
* 不能显式指定任何其他基类。
* 静态类不能实现接口(接口不能包含静态方法,而静态类不能包含实例方法)
* 静态类的成员不能有protected或protected internal访问保护修饰符。
【Note】扩展方法
1.定义一个静态类以包含扩展方法。该类必须对客户端代码可见。
2.将该扩展方法实现为静态方法,并使其至少具有与包含类相同的可见性。
3.该方法的第一个参数指定方法所操作的类型;该参数必须以 this 修饰符开头。
4.在调用代码中,添加一条 using 指令以指定包含扩展方法类的命名空间。
5.按照与调用类型上的实例方法一样的方式调用扩展方法。
(使用扩展方法最好的途径是通过继承来特化一个类型。如果扩展方法的签名跟类型已有方法的签名一样,那么扩展方法就会被覆盖。通过调用代码很难判断一个方法是不是扩展方法,如果对源代码没有控制权。问题就更加严重)
【Note】const
* const字段自动成为static字段
*const 字段只能在该字段的声明中初始化
【Note】readonly
* 与const不同,readonly只能用于字段,被声明的字段只能在构造器中修改,或者直接在声明时指定
* 与const不同,readonly字段可以是实例字段,也可以是静态字段,每个实例字段的值可以不同
*关键区别 const 字段为编译时常数,而 readonly 字段可用于运行时常数,如下例所示:
public static readonly uint timeStamp = (uint)DateTime.Now.Ticks;
*如果将readonly用于数组,只会冻结地址,而不会冻结内容
【Note】分部类
partial class className{} (原来的类也要加上partial)
*分部类不允许对编译好的类(其他程序集的类)进行扩展,只能运用与同一程序集
【Note】分部方法
在原来的类提供声明,在新增的类提供实现
* 分部方法只能是void,不能使用out,可以使用ref
*由于上面的规则,使得分部方法不必实现也不会产生任何影响
【Note】基类和派生类之间的转型
派生类可以隐式转换成基类,隐式转换不会为基类实例化一个新的实例,相反同一个实例会被引用为基类型
【Note】private
“派生类不能访问基类的private成员”
*假如派生类同时是基类的一个嵌套类,就不成立了
【Note】protect
基本规则:要从一个派生类中访问一个对象的受保护的成员,必须在编译的时候确定受保护的成员是派生类的一个实例
例: public class PadItem
{
protected string Name;
}
public class Contact: PadItem
{
void Load(PadItem padItem)
{
padItem.Name=... //这里出错
}
}
【Note】C#多重继承的一般解决方案————聚合
public class A {} //需要被继承的类
public class B //需要被继承的类
{
protect int first ;
}
public class C : A //C作为基类,直接继承A
{
private B BIntence { get ; set ;}
public int FIRST
{ get{return B.first} set{B.first=value}}
}
【Note】virtual
基类: virtual修饰方法
派生类: override 修饰方法
* virtual不能与static同时使用,因为当一个方法被声明为Static时,编译器会在编译时保留这个方法的实现(它属于类),而当一个方法被声明为Virtual时,直到你使用ClassName variable = new ClassName();声明一个类的实例之前,它都不存在于真实的内存空间中
* 不要在构造器中调用会影响所构造对象的任何虚方法,与C++不同(C++构造期间的virtual方法总是调用基类的实现),C#是根据设计原则:总是调用派生得最远的虚方法,即使构造函数尚未完全执行
【Note】C#规定只能使用下面这些限定符中的一个:
override virtual static abstract sealed
代表的含义分别为:
重载函数、虚拟函数、静态函数、抽象函数、密封函数(不可派生)
【Note】new实现的重载
在C#的角度看,new唯一的作用就是消除编译的警告
new跟override区别: new的重载没能实现多态
【Note】abstract类
次要特征:不可实例化
主要特征:类包含抽象成员
基类方法用abstract修饰
派生类用override修饰重载
*抽象方法是隐式的虚方法,其实现只能由派生类实现,而vertuial标记的虚方法可以由基类实现
【Note】is
public static void Save(object data)
{
if (data is string )
{
data=Encrypt((string)data); //加密数据
}
}
*在使用is之前,可以优先考虑多态性,比如在上面的例子中,从一个通用的基类派生,然后将这个基类类型作为Save()方法的一个参数调用,就可以避免显式地检查string
* as是强制转换类型,is是判断类型,假如一个对象object可以as成string,但他不一定is string判断的时候是true,as只能作用与引用类型,转换失败不引发异常
【Note】接口
* 区别:通过基类来共享成员签名,而接口只是共享成员签名
* 命名规范:采用Pascal大小写规范,第一个字母I开头
* 不包含实现,不包含字段,可以包含属性
* 所有成员不能有修饰符,CLI默认是public
* 接口不能包含static
* 接口不能显方用abstract修饰,因为CIL默认了abstract
【Note】接口的显式实现和隐式实现
* 显式实现不能添加修饰符,成员名称前面要添加接口名和一点作为前缀
* 显式实现的访问:实现接口的类的对象不能直接访问实现的方法,要先转换成接口对象,由接口对象来访问
* 隐式实现 修饰符只能是public,可选属性是virtual,如果没virtual,则该成员默认为sealed
* 总之,采用显式实现,接口方法就不作为实现接口的类的类成员,采用隐式实现,接口对象和实现接口的对象都能看见
【Note】接口设计的原则
(1)接口成员书不是核心的类功能?
如果接口方法单纯只是辅助方法,那就采用显式实现,若改接口方法是该类的主要功能,就采用隐式实现
(2)接口成员名作为类成员是否恰当?
名称是否产生歧义?总之一个接口成员在类中的用途不是很明确,就采用显式
(3)是否已经有一个同名的类成员?
【Note】接口的继承
interface IFirstface
{
int getNum();
}
interface ISecondface : IFirstface
{
int getNum2();
}
class Myclass : ISecondface
{
int IFirstface.getNum() //!!这里必须引用最初声明它的那个接口
{
return 1;
}
int ISecondface.getNum2()
{
return 2;
}
}
方法引用问题:
Myclass my = new Myclass();
ISecondface Is = my;
Is.getNum(); //!!派生的接口可以访问所有接口成员
Is.getNum2();
【Note】扩展方法在接口的应用
* 不要跟分部方法混淆了
【Note】通过接口来实现多重继承
下面例子是使用了 聚合和接口的方法:
class A{}
interface IB //IB确保B类和复制到C类的成员有
{ //一致的签名
string Str
{
get;
set;
}
}
class B:IB
{
public string Str
{
get { return "getStr";}
set { }
}
}
class C : A, IB
{
private B _B;
public string Str
{
get { return _B.Str; }
set { _B.Str = value; }
}
}
但是添加到B类的新成员不会同时添加到C类上,这还没有做到与“多重继承”同义
解决办法:
如果被实现的成员是方法(不是属性)的情况,
可以采用扩展方法对IB进行扩展,
这样凡是实现了IB的类都有了扩展的新方法
【Note】版本控制
* 扩展功能的办法 从一个原有的接口派生出一个新的接口
【Note】抽象类跟接口的比较
-----------------------------------------
抽象类:不能脱离它的派生类来实例化。抽象类构造器只能由它门的派生类调用
接口:不能实例化,不能有构造器
--------------------------------------------
抽象类:定义了实现类必须实现的抽象成员签名
接口:接口的所有成员要在实现类实现,不能只实现部分成员
-------------------------------------------
抽象类:扩展性比接口号,不会破坏任何版本的兼容性。在抽象类中,可以添加附加的非抽象成员,它们可以由所有派生类继承
接口:如果添加更多的成员扩展成员,会破坏版本兼容性
-----------------------------------------------
抽象类:可以包含存储在字段中的数据
接口:不能存储任何字段。只能在派生类中指定字段。解决办法就是在接口中定义属性,但不能包含实现
--------------------------------------------
抽象类:可以包含实现的virtual成员,所以能为派生类提供一个默认的实现
接口:所有成员自动成为virtual成员,而且不能包含任何实现
-----------------------------------------
抽象类:会占用之类唯一一个基类选项(单继承)
接口:最然不允许默认实现,但是实现接口的类可以继续相互派生
【Note】结构体
--------------------------
一个良好习惯:
应该确保值类型不变的。属性只有get而没有set。如果确实要修改,应该通过一个方法返回新的实例。 *思考为什么?*
-----------------------------------
* suruct跟类差不多,可以有字段,属性,方法,可以定义含参构造器,但是不能显式定义无参构造器(有时候根本不会调用构造器,比如定义数组的的时候,会直接采用默认值初始化,若可以自定义默认构造器,那么有时调用,有时不调用,所以初始化不统一了,因此C#禁止struct自定义默认构造器)
* 不能声明一个字段的同时进行初始化,原因同上
* 支持含参构造器,要求必须对所有字段初始化,否则编译报错
* 值类型都是密封的
* 值类型继承链:object->ValueType->struct
* 值类型还可以实现接口
【Note】必须要拆箱成为基础类型,例int 装箱成object,那只能拆箱成int,不能double之类的其他类型
【Note】lock语句不能用于值类型
假如用了值类型,那么就会装箱成堆的一个引用,与原来在栈中的引用对比总是不同的
【Note】说明值类型(struct)不可变的重要性
----------------------------------
IAngle是声明方法MoveTo的接口
Abgle是实现IAngle接口的类
其中MoveTo在Angle中改变其字段value的值,而不是返回一个新值的新实例
--------------------------------------
Angle angle = new Angle(25);
object objectAngle = angle;
((Angle)objectAngle).MoveTo(26);
输出:(Angle)objectAngle.value //25
((IAngle)angle).MoveTo(26)
输出:((Angle)angle).value // 25
((IAngle)objectAngle).MoveTo(26)
输出:((Angle)objectAngle).value //26
【Note】避免拆箱
* 拆箱指令不包括将数据复制回栈的动作
* 接口是引用类型,当通过接口访问已装箱的值时,拆箱和复制就可以避免
【Note】枚举之间的转换
MyEnum mm = MyEnum.A;
MyEnum2 mm2 = (MyEnum2) (int) mm;
----------
下面枚举数组的转换:
MyEnum[] mm= (MyEnum[])(Array) new MyEnum2[42];
这里利用了CLR的赋值兼容性比C#宽松的事实,
先转换成数组,在转换成第二个枚举,
前提是两个枚举具有相同的基础类型
【Note】枚举作为标记使用
[Flags]
enum MyEnum
{
A = 1<<0,
B= 1<<1
}
MyEnum mm = MyEnum.A | MyEnum.B;
Console.WriteLine(mm);
输出: A,B
如果没有 [Flags]特性,就会输出3
【Note】重写object的成员
* 重写ToString()
* 重写GetHashCode()
* 重写Equals(),必须同时重写GetHashCode()
【Note】重载运算符没看
【Note】类型封装
* 没有任何访问修饰符的类会被定义成internal(排除嵌套类,它默认是private)
* 命名空间中的类型声明可以只可以具有 public 或 internal 访问,默认internal
* 嵌套类可以使用public,internal,private,protected,protected internal
----------------------
嵌套类修饰符:
public:如果包容类是internal,则成员只在内部可见,如果是public,那么就可以从程序集外部访问
internal:成员只能从当前程序集访问
private:成员只能从包容类访问
protected:成员可以从包容类,以及派生的任何之类中访问,不管程序集是哪个
protected internal:成员可以从同一程序集的任何地方访问,并且可以从包容类型的任何派生类中访问,即使派生类不在同一程序集中
【Note】命名空间
可以嵌套定义:
namespace aaaa
{
namespace bbbb
{
}
}
也可以直接这样
namespace aaaa.bbbb
{
}
在CLR中是一样的
【Note】垃圾回收
* 在一些关键代码运行之前,可以先调用System.GC的Collect()方法,显著减少GC运行的可能性
* 弱引用(System.WeakReference)
private WeakReference Data;
public FileStream GetData()
{
FileStream data = (FileStream)Data.Target;
if (data != null)
{
return data;
}
else
{
//重新加载数据到弱引用Data
data = (FileStream)Data.Target;
return data;
}
}
上面代码要注意:
这里要先将弱引用Data的数据先赋给data先,
从而避免在"检查null"和"访问数据"两个动作之间,
垃圾回收器将弱引用清除
【Note】终接器
* 声明定义:~类名(){ }
* 终接器不负责回收内存,它主要职责是释放像数据库连接和文件句柄之类的资源
【Note】使用using进行确定性终结
* 终接器的调用是不确定性的,所以它只能作为后备机制
* 实现IDisposeable接口
public coid Dispose()
{
Close();
System.GC.SuppessFinalize(this);
//SuppessFinalize的作用是将this从终结队列(f-reachable)
//中移除,移除之后才能真正成为垃圾
}
* 使用using代码块,或者try/finally
【Note】资源利用和终结的指导原则
* 最好避免重写Finalize方法(总结方法),这会推迟垃圾回收,如果是数组,那么数组里面每一个对象都要执行一次终结方法
* 有终接器的对象应该实现IDisposeable来支持确定性终结
* 终接器应避免任何未处理的异常
* 像Dispose(),close()这样的方法应该调用System.GC.SuppessFinalize(this)使垃圾更快清理
* 资源清理方法应该足够简单,着重清理引用的资源,不要再引用其他对象
* 若基类实现了Dispose(),派生类应该调用基类的实现
* 调用Dispose()之后,对象不能再使用,出了调用Dispose(),Dispose()可以多次调用
【Note】延迟初始化
* 可以在属性的get里面判断为null的时候才进行成员初始化
*新方法:采用System.Lazy<T>
【Note】异常处理的指导原则
* 只捕捉你能处理的异常
* 不要隐藏你不能完全处理的异常
* 尽可能的少用Ststem.Exception
有些异常如OutOfMemoryException和StankOverflowException,这些异常不能捕捉到不处理,在CLR4中会采取关闭应用程序作为最佳操作,所以捕捉到应该保存易丢失数据,然后马上关闭程序,或者throw语句重新引发异常。
* 避免在调用栈较低的位置报告或记录异常(不记录异常的原因是 怕高级的栈重复引发异常导致重复记录)
* 在一个catch块中使用throw而不是throw <异常对象>(否则栈追踪不到原始位置)
* 重新引发不同的异常要小心
不仅会重置引发点,还会隐藏原始异常
【Note】模版接口使得类可以重复实现一个接口
class tempClass: IContainer<Address>,IContainer<Phone>
改进措施: 使用IContainer<object>
【Note】泛型类/接口的构造器
* 构造器跟普通类或者接口的构造器就行了,不能添加<T>,否则编译不能通过
* 在构造器赋值过程中,由于不知道T的具体类型,所以可以采用default()方法赋初值
【Note】Tuole 元组
* Tuple<int,string> temp= new Tuple<int,string>(20,"str");
* Tuple<int,string> temp= Tuple.Create(20,"str");
如果参数很多就用工厂方法create(),否则new一个元组太长了
【Note】接口约束
情景:在一个泛型类里面,因为要对T类型的对象排序,所以将对象转换成IComparable<T>对象,一般情况可以运作,但是如果传进来的T类型没有实现接口的话,必然出错。跟C++不同,C#不支持在类型参数上调用运算符(+,-,*等),它们是静态的,不能表现为接口或者基类约束的形式.
接口约束: class tempClass<T> where T:System.IComparable<T>
【Note】基类约束
* class tempClass<T> where T:基类名
【Note】struct/class约束
* class tempClass<T> where T:struct (这里约束T必须为值类型)
* struct/class约束不能与基类约束一起使用,因为那是没意义的
【Note】构造器约束
* class tempClass<T> where T:new() (这里约束T必须实现默认构造器)
* 不能约束含参构造器
【Note】含有约束的继承
* 约束可以由一个派生类进行继承,但是派生类必须显式地写出所有的约束,这样的设计是为了提高程序员使用派生类时的认知度,避免发现有约束时,却不知道约束从哪里来
* 相反情况:重写一个虚泛型方法,或者创建一个显式接口方法实现时,约束是隐式继承的,显式写出来反而编译出错
【Note】不允许的约束
* 泛型 不能提出对运算符的要求
比如方法签名(T first,T second),不能进行first+second运算
因为没有办法限制一个类必须有一个static方法,例如,接口不能指定static方法
* 泛型约束不支持OR条件
class tempClass<T> where T:IComparable<T>||Ifrmattable(这是错的)
* 不能约束 类型参数是委托或者枚举类型
【Note】泛型方法中的转型
有时候应该避免使用泛型
如下面代码:
public static T Deserialize<T>( Stream stream,Iformatter formatter)
{
return (T)formatter.Deserialize(stream);
}
执行formatter.Deserialize(stream)会返回一个object
在泛型方法里面执行转型,假如没有约束来验证转型的有效性,
那么级一定要非常小心了
但是真正调用的时候是下面情况:
string greeting=
xx.Deserialize<string>(stream,formatter)
上面代码看起来好像是强类型的
但实际上已经进行了隐式转换
如果改成:
string greeting=
(string)xx.Deserialize(stream,formatter)
这样应该更好描述代码所发生的事情
【Note】协变与逆变(主要应用与接口和委托)
参考文章:
http://www.cnblogs.com/artech/archive/2011/01/13/variance.html
例:
public delegate TResult Func<in T, out TResult>(T arg);
简单理解:
协变:父类引用子类
逆变:子类引用父类
在委托中,返回值用out 参数是属于输入值,用in
...
下面提起几个泛型协变和反变容易忽略的注意事项:
1. 仅有泛型接口和泛型委托支持对类型参数的可变性,泛型类或泛型方法是不支持的。
2. 值类型不参与协变或反变,IFoo<int>永远无法变成IFoo<object>,不管有无声明out。因为.NET泛型,每个值类型会生成专属的封闭构造类型,与引用类型版本不兼容。
3. 声明属性时要注意,可读写的属性会将类型同时用于参数和返回值。因此只有只读属性才允许使用out类型参数,只写属性能够使用in参数。
推理:
如果一个接口需要对T协变,那么这个接口所有方法的参数类型必须支持对T的反变
推理的例:
nterface IFoo<in T>
{
}
interface IBar<out T>
{
void Test(IFoo<T> foo);
}
如果一个接口需要对T进行协变或反变,那么这个接口所有方法的返回值类型必须支持对T同样方向的协变或反变
【Note】委托的声明
* C#编译器不允许定义一个直接或间接通过System.Delegate派生的(System.MuticastDelegate是System.Delegate的系统派生的子类)
* 使用delegate关键字声明一个委托数据类型
public delegate void MyFirstDelegateHander(int par1,string par2);
* 若委托是声明在一个类里面,那它便是一个嵌套类了
* 例如上面定义的MyFirstDelegateHander委托,是一个引用类型,但是不必使用new来实例化,直接传递函数名称,而不是实例化,这是C#2.0开始支持的委托推断
【Note】匿名方法与委托
* xxxHander xx= delegate(int first,int second){ return "asdasd"}
这里的delegate声明了一个委托字面量
*匿名方法允许省略参数列表,甚至委托类型发生了变化,只要返回类型与委托的返回类型兼容
delegate{ return "xxxx"}
【Note】系统定义的委托:Func<> Action<>(.net3.5开始存在)
【Note】Lambda
前提背景:delegate bool ComparisonHaander(int first,int second)
(int first,int second)=>{ return first<second}
还可以省略参数类型:
( first, second)=>{ return first<second }
无参数的情况:
Func<string> getUserInput=()=> {..return "input value"}
linq中:
表达式Lambda:
dx.tb_attachment.Where(item => item.a_id==1);
语句Lambda:
dx.tb_attachment.ToList<tb_attachment>().Where(item => { int a; return item.a_id == 1; });
【Note】Lambda表达式不是clr内部固有构造,它们的实现是由C#编译器编译时生成的
在clr中,匿名方法会被转换成一个单独的有编译器生成的静态方法
【Note】Lambda表达式使用外部变量(闭包)
如果匿名函数使用了外部变量,那么当这个委托执行的时候依然可以使用,这种现象称之为闭包(在clr中会定义一个闭包,外部变量和匿名函数都将定义成为这个闭包的实例成员)
【Note】表达式树(没看懂什么意思)
1.Lambda表达式作为数据使用
dx.tb_attachment.Where(item => item.a_id==1);
这里表达式会转换成sql语句发送到数据库,让数据库筛选得到数据再返回
2.表达式叔作为对象图使用(??????)
3.Lambda表达式和表达式树的比较(???)
4.解析表达式树(???)
【Note】多播委托来实现Observer模式
* 声明在发布者类里面的委托是嵌套类,其他类里面是不能使用TemperatureChangeHandler的
public delegate void TemperatureChangeHandler(float newTemperature);
* 调用一个委托之前谨记要判断委托是否为null
判断方法:
TemperatureChangeHandler localOnChange = OnTemperatureChange;
if (localOnChange!=null)
{
localOnChange(value);
}
要先将委托复制到一个局部变量,防止判空和调用委托之间所有订阅者被移除(由一个不同线程),否则为空的时候会出错
* 委托虽然是个引用类型,但是增加或者减少一个委托都会重新返回个全新的多播委托
【Note】多播委托一般使用-= 和+=,但是使用了复制符号=之后,会用新的订阅者代替它们,为解决这个问题,需要使用事件;
【Note】多播委托链中一旦其中一个订阅者发生异常中断,后续的订阅者就接收不到通知
解决这个问题,可以在发布者触发委托的时候用try/catch 逐个逐个触发
【Note】还有个情形需要逐个触发委托:如果委托有返回值或者参数有ref 或者out的时候
【Note】总结委托存在的问题
1.错误的使用赋值符号(事件订阅的封装性问题)
2.从事件包容者外部触发事件(事件发布的封装性问题)
3.普通委托另一个弊端,就是容易忘记在调用委托之前检查null值
采用event:
public delegate void TemperatureChangeHandler(object sender,TemperatureArgs e);
public event TemperatureChangeHandler OnTemperatureChange = delegate { };
可以解决上述问题:
1.event禁止赋值
2.event不允许在类外部执行触发
3.初始值delegate { },可以不必每次检查null,因为event限制事件的赋值只能发生在类的内部
采用泛型委托版本:
public delegate void EventHandler<T> (object sender,T e) where T:EventArgs;
由于从C#2.0开始内置上述代码,说可以改写成:
public event EventHandler<TemperatureArgs> OnTemperatureChange = delegate { };
【Note】自定义事件
* 可以修改委托的作用域,使它private变成protect
* 可以添加订制的add和remove
【Note】为什么委托定义的返回值通常都为void?
尽管并非必需,但是我们发现很多的委托定义返回值都为void,为什么呢?这是因为委托变量可以供多个订阅者注册,如果定义了返回值,那么多个订阅者的方法都会向发布者返回数值,结果就是后面一个返回的方法值将前面的返回值覆盖掉了,因此,实际上只能获得最后一个方法调用的返回值。可以运行下面的代码测试一下。除此以外,发布者和订阅者是松耦合的,发布者根本不关心谁订阅了它的事件、为什么要订阅,更别说订阅者的返回值了,所以返回订阅者的方法返回值大多数情况下根本没有必要。
【Note】匿名类型和隐式类型
var par = new { value1 = "par_value1", value2 = "par_value2" };
var par2 = new { par.value1, value2 = "par2_value2" };
var par2内部的 par.value1会自动生成一个值为par_value1的字段value1
注意事项:
* 两个匿名类型想要生成的CIL类型相同,就要做到属性名,数据类型,和属性的顺序完全匹配,即,如果这些条件不符合,那么两个不同的匿名类型的实例就不可以从一个转换到另一个
* 匿名类型的实例,是不可变的
* 匿名类型是c#3.0支持"投射"的关键
【Note】匿名类型不能使用集合初始化器
解决方案1:
定义一个方法:
static List<T> CreateList<T>(T t) { return new List<T>(); }
利用方法类型推导:
var myvar = new
{
name = "lihuixian001",
pwd = "123456"
};
var pp= CreateList(myvar);
解决方案2:
使用数组初始化器:
var myVar= new[]
{
new{par1="value1",filed="value2"},
new{par1="value1",filed="value2"}
};
最终得到一个匿名类型构成的一个数组,由于是数组,所以每个项的类型都要相同
【Note】IEnumerable<T>
公开枚举器,该枚举器支持在泛型集合上进行简单迭代
集合类实现IEnumerable<T> 集合类的状态靠实现IEnumerator<T> 的类来维持
* foreach循环内不允许对item赋值,但是可以修改item内部成员都值
【Note】标准查询运算符
实现IEnumerable<T>的类型都会增加一个GrtEnumerator()方法,同时添加using System.Linq可以自动扩展N多方法,对于IEnumerable<T>上的每个方法都是一个标准查询运算符
【Note】LINQ的并行运行(.net4.o)
AsParallel()扩展方法
【Note】LINQ的推迟执行!!!!!!!
* IEnumerable<T>.Where()里面的Lambda表达式并不是数据,而是一个委托,它将发送到数据源那里执行,声明的时候它并没有执行
* 会触发Lambda表达式执行的语句
1. foreach
2.Enumerable的Count函数
3.ToArry()函数
* 调用ToXXX()函数会将查询结果保存起来
例如: IEnumerable<T>.Where().ToList()
这样下次就不用去数据源重新读取数据了
【Note】OrderBy() ThenBy() Join() GroupBy() SelectMany()
【Note】标准查询运算符的查询表达式语法
* “标准查询运算符”是组成语言集成查询 (LINQ) 模式的方法。 大多数这些方法都在序列上运行,其中的序列是一个对象,其类型实现了 IEnumerable<(Of <(T>)>) 接口或 IQueryable<(Of <(T>)>) 接口。
* 某些使用更频繁的标准查询运算符具有专用的 C# 和 Visual Basic 语言关键字语法,利用这些语法,将可以在“查询表达式”中调用这些运算符。 与“基于方法”的查询表达形式相比,查询表达式是一种不同的、可读性更好的查询表达形式。 在编译时,查询表达式子句将被转换为对查询方法的调用。
IEnumerable 用于Linq to object
IQueryable 用于Linq to SQL / Entity Framework比较好
【Note】IComparable<T>
包含CompareTo()方法, 实现比较大小功能
【Note】IComparer<T> 实现排序功能
包含Compare()方法
【Note】ICollection<T>
IList<T>和IDictionary<TKey,TValue>都是从ICollection<T>派生
包含:
Count属性
CoptyTo()方法:允许集合转换成数组
【Note】主要集合类
* List<T>: 列表集合
* Dictionary<TKey,TValue>:字典集合
* SortDictionary<TKEY,TValue>和SortedList<T>:已排序集合
前者按照键排好序,后者是按值
* Stack<T>:栈集合
* Queue<T>:队列集合
* LinkedList<T>:链表
【Note】索引运算符
接口里面可以声明属性
T this[xx类型 index]
{
get;
}
【Note】迭代器
利用:IEnumerable<T>和IEnumerator<T>
【Note】使用System.Type访问元数据
1. GetType()(实例方法)
2.无法或者实例,可以用typeof()表达式
【Note】Type类型的实例方法
GetProperties()
GetProperty()
GetField()
例:
FieldInfo info = type.GetField("haha");
info.SetValue(date2, "xiugaile");
【Note】泛型类型上的反射
例:
Type type;
type = typeof(System.Nullable<>);
Console.WriteLine(type.ContainsGenericParameters);
Console.WriteLine(type.IsGenericType);
type = typeof(System.Nullable<DateTime>);
Console.WriteLine(type.ContainsGenericParameters);
Console.WriteLine(type.IsGenericType);
获取泛型类型或者方法上的类型参数
实例方法:GetGenericParameterConstraints() 返回Type数组
【Note】特性
包含前缀的特性
[module:xxx] [return:xxx] [assembly:xxx]
自定义定义一个特性:
public class xxxxAttribute : Attribute
{}
应用一个特性的时候,可以只写[xxxx]或者[xxxxAttribute ]
编译器会自动考虑命名规范,可以加Attribute后缀或者
不加
查找一个特性:
Attribute[] attributes =
(Attribute[])property.GetCustomAttributes(
typeof(CommandLineSwitchRequiredAttribute),
false);
查找特性的方法通常定义在自定义的特性类里面,
作为一个静态方法
* 特性类可以有构造器
构造器的限制,向特性的构造器传递参数的时候,参数
只能是字面量或者类型(比如typeOf(int))
【Note】System.AttributeUsageAttribute限制特性
* [AttributeUsage(AttributeTargets.Property|AttributeTargets.Field)]
public class xxxxAttribute : Attribute
{}
这里限制特性只用于属性`或者字段
* [AttributeUsage(AttributeTargets.Property,具名参数名称=xx)]
具名参数指定成员属性或者字段的默认值
【Note】一些预定义的特性
预定义的特性有影响编译器很运行时的功能
【Note】1.System.Diagnostics.ConditionalAttribute
功能类似与#if/#endif
* 不同于#if/#endif,它标识的代码还是会编译成CIL,取决于调用者所在的程序集的预处理器标识符,而不是被调用者的
* 该特性只能标识于方法或者类
* 若方法包含out参数或者有返回值,就不能标识这个特性
* 只有继承于System.Attribute的类才能标识此特性
* 假如该特性标识于特性,那么被标识的特性符合条件才能通过反射被查找出来
例: #define CONDITION_A
Console.WriteLine("Begin...");
MethodA();
MethodB();
Console.WriteLine("End...");
[Conditional("CONDITION_A")]
static void MethodA()
{
Console.WriteLine("MethodA() executing...");
}
[Conditional("CONDITION_B")]
static void MethodB()
{
Console.WriteLine("MethodB() executing...");
}
【Note】2.ObsoleteAttribute
向调用者发出特定的成员或者类型已过时的警告
【Note】3.SerializableAttribute和NonSerializedAttribute
序列化和反序列化
using (steam = File.Create(doc.Title + ".LHX"))
{
BinaryFormatter bin = new BinaryFormatter();
bin.Serialize(steam, doc);
}
document doc2;
using (steam=File.Open(doc.Title + ".LHX",FileMode.Open))
{
BinaryFormatter xx = new BinaryFormatter();
doc2=(document) xx.Deserialize(steam);
}
自定义序列化
* 为了实现序列化,要实现ISerializable接口,包含一个GetObjectData()方法
* 为了支持反序列化,要实现形式如
public EncryptableDocument(
SerializationInfo info, StreamingContext context)
的构造器
序列化的版本控制
* 为了兼容旧版本,可以用OptionalField特性标记(.net2.0)
* .net2.0之前的版本只能通过实现ISerializable接口只保存并且读取可用的字段
SerializableAttribute在CIL中是个伪特性
【Note】dynamic的原则和行为
* dynamic涉及一个解释机制,当运行时遇到一个dynamic调用时,它将请求编译成CIl,再调用新的调用
* 任何类型都可以转换成dynamic
* 从dynamic转换到一个替代类型是需要基础类型的支持
* dynamic的基础类型可以改变,不同于var隐式类型
* 要到运行时才验证dynamic上指定的签名是否存在
* 任何dynamic成员的调用都是返回一个dynamic对象,像data.ToString()也是返回一个dynamic对象,但是执行时,在dynamic对象调用GetType()是返回编译好的类型
* 用dynamic实现的反射不支持扩展方法
* dynamic本质是一个object,与object区别的关键是它的特殊动态行为会调用时出现
【Note】实现自定义动态对象
* 优先方案是I继承DynamicObject类
* 也可以实现IDynamicMetaObjectProvider接口
【Note】多线程要求:保持线程的原子性,避免死锁,避免造成执行时的不确定性(如多个线程进行竞争的情况)
【Note】yield
* yield 语句只能出现在 iterator 块中,该块可用作方法、运算符或访问器的体。
这类方法、运算符或访问器的体受以下约束的控制:
1.不允许不安全块。
2.方法、运算符或访问器的参数不能是 ref 或 out。
* yield 语句不能出现在匿名方法中
static IEnumerable<Vector> GetVectors()
{
yield return new Vector(1, 1);
yield return new Vector(2, 3);
yield return new Vector(3, 3);
}
上面的函数不能用List<T>返回值
接收的结果要ToList():
List<Vector> vectors = GetVectors().ToList();
类似于linq的延迟绑定
【Note】TPL和PLINQ是.net4.0的,但是引入 Reactive Extension(Rx)的程序集也可以用于.net3.5
class Program
{
static void Main(string[] args)
{
string name = "Thief-X";
Task<string> task = Task.Factory.StartNew<string>(()=>getCount(name));
foreach (char item in BusySymbols())
{
if (task.IsCompleted)
{
Console.Write('\b');
break;
}
Console.Write(item);
}
Console.Write(task.Result);
}
public static string getCount(string str)
{
System.Threading.Thread.Sleep(5000);
return "哈老~!?"+str+",结果出来啦~!!!";
}
public static IEnumerable<char> BusySymbols()
{
string busySymbols = @"-\|/-\|/";
int next = 0;
while (true)
{
yield return busySymbols[next];
System.Threading.Thread.Sleep(100);
next = (++next) % busySymbols.Length;
yield return '\b';
}
}
}
【Note】Task的一些属性
* Status Task状态的枚举值
* IsCompleted
* Id
* AsyncState
提供额外的数据。例如多任务计算List<T>中的值,由于调用List<T>.Add()不是跨多线程的安全操作,所以一个办法就是将包含结果的列表索引存储在AsyncState
* Task.CurrentId
【Note】Task.Factory.StartNew() 使用带参数的委托作为工厂方法的参数
1.定义一个委托:Func<object,string> mmm=getCount
Task<string> task=Task.Factory.StartNew<string>(mmm,name);
2.使用闭包:
Task<string> task=Task.Factory.StartNew<string>(()=>getCount(name));
【Note】ContinueWith()
* ContinueWith()会返回一个task
* 同一个task执行ContinueWith()多次,那么当task执行完成之后,登记在它上面的后续线程都同时并发执行
【Note】task上的异常处理
* Task执行期间的异常都会被禁止,一直到调用某个任务完成
Wait() Result Task.WaitAll() Task.WaitAny()
try
{
task.Wait();
}
catch (AggregateException exception)
(注:AggregateException 是个异常集合)
* 另外一种处理方法:使用ContinueWith()
bool parentTaskFaulted = false;
Task task = new Task(() =>
{
throw new ApplicationException();
});
Task faultedTask = task.ContinueWith(
(parentTask) =>
{
parentTaskFaulted = parentTask.IsFaulted;
}, TaskContinuationOptions.OnlyOnFaulted);
task.Start();
faultedTask.Wait();
Trace.Assert(parentTaskFaulted);
if (!task.IsFaulted)
{
task.Wait();
}
else
{
Console.WriteLine(
"ERROR: {0}", task.Exception.Message);
}
【Note】协作式取消任务(.net)
* 在主线程建立CancellationTokenSource类对象cancelsoure,调用Cancel()可发出退出指令
* 子线程上判断cancelsoure.Token.IsCancellationRequested
* Task.Factory.StartNew(子线程委托,cancelsoure.Token)
CancellationToken上的Register()方法,线程结束时候执行的委托:
cancelsoure.Token.Register(() => { Console.WriteLine("子线程结束了"); });
【Note】长时间的任务
Task.Factory.StartNew()默认是从共享线程那里分配一个线程,但是如果是长时间任务的话,应该指定TaskCreationOptions.LongRunning参数,这样线程池更有可能分配一个专门的线程
【Note】释放一个任务
可以调用一个Dispose()
【Note】并行迭代
* Parallel.For()
Parallel.For(0, iterations, (i) =>
{
sections[i] += PiCalculator.Calculate(
BatchSize, i * BatchSize);
});
pi = string.Join("", sections);
* Parallel.ForEach()
API会使用爬山算法来提高性能
Parallel.ForEach(files, (fileName) =>
{
Encrypt(fileName);
});
【Note】捕捉并行迭代的异常
try
{
Parallel.ForEach()
}
这里将会捕捉到AggregateException,所有异常包含在这个集合
【Note】并行迭代的取消
类似于task
【Note】并行结果和选项ParallelOptions
* parallelOptions.MaxDegreeOfParallelism
最大并行度,1相当于关闭
* parallelOptions.TaskScheduler `任务调度器
对task的执行有完全的控制:
包括任务的执行顺序以及在什么线程执行
比如:先进先出,后进先出顺序之类的
* parallelOptions.CancellationToken取消标志
例:
Parallel.ForEach(
files, parallelOptions,
(fileName, loopState) =>
{
Encrypt(fileName);
});
* 在循环内部,loopState.break()可以中断退出循环并取消进一步迭代
* Parallel.ForEach()返回结果:
IsCompleted 指示所有迭代是否完成
LowestBreakIteration最低迭代的索引
【Note】并行执行LINQ查询
标准查询运算符使用PLINQ:
OrderedParallelQuery<string> parallelGroups = data.AsParallel().OrderBy(item => item);
查询表达式使用PLINQ:
ParallelQuery<IGrouping<char, string>> parallelGroups;
parallelGroups =
from text in data.AsParallel()
orderby text
group text by text[0];
务必小心不要让多个线程不恰当地同时访问并修改相同的内存
【Note】取消PLINQ查询
【Note】.net4.0之前的并行
System.Threading
【Note】变量读写的原子性
一个类型的大小假如不超过一个本地整数,那么该类型是原子性的,比如64位操作系统将保证long型原子性,但是decimal(128位)的就不行了
【Note】Monitor同步
bool lockTaken = false;
try
{
Monitor.Enter(_Sync, ref lockTaken);
_Count++;
}
finally
{
if (lockTaken)
{
Monitor.Exit(_Sync);
}
}
其中_Sync 的定义:
readonly static object _Sync = new object();
【Note】lock
lock (_Sync)
{
_Count++;
}
其中_Sync 的定义:
readonly static object _Sync = new object();
【Note】使用System.Threading.InterLocked同步
代替高带价的Monitor和lock
InterLocked.Exchange(location,value,comparand)
【Note】避免锁定this,typeof(tyoe),和string
【Note】其他一些同步方法:
1.System.Reflection.Mutex类同步
可以多个进程之间同步,好处是可以限制应用程序,对与同一个应用程序只能打开一个
2.WaitHandle
Mutex的基类
.
.
.
【Note】异步编程模式(APM)
基于事件的异步模式(EAP)
Background Worker模式