• [读书笔记]《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模式

  • 相关阅读:
    线程同步
    快捷键之Sublime
    快捷键之Idea
    快捷键之Chrome
    20155219付颖卓《网络对抗》逆向及Bof基础
    20155219 《嵌入式基础》
    20155219 《信息安全系统设计基础》课程总结
    20155219 《信息安全系统设计基础》第十四周学习总结
    20155219 《信息安全系统设计基础》第十三周学习总结
    2017-2018-1 20155219《信息安全系统设计基础》 实验五 通讯协议设计
  • 原文地址:https://www.cnblogs.com/lihuixian001/p/2325990.html
Copyright © 2020-2023  润新知