【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模式
|
请发表评论