《C#高级编程》读书笔记

《C#高级编程》读书笔记

  1. C#类型的取值范围html

    名称 CTS类型 说明 范围
    sbyte System.SByte 8位有符号的整数 -128~127(27−27~27127−1)
    short System.Int16 16位有符号的整数 -32 768~32 767(215−215~2151215−1)
    int System.Int32 32位有符号的整数 -2 147 483 648~2 147 483 647(231−231~2311231−1)
    long System.Int64 64位有符号的整数 263−263~2631263−1
    byte System.Byte 8位无符号的整数 0~255(0~28128−1)
    ushort System.UInt16 16位无符号的整数 0~65535(0~2161216−1)
    uint System.UInt32 32位无符号的整数 0~4 294 967 295(0~2321232−1)
    ulong System.UInt64 64位无符号的整数 0~18 446 744 073 709 551 615(0~2641264−1)
  2. 访问限制符正则表达式

    修饰符 应用于 说明
    public 全部类型或成员 任何代码都可以访问该项
    protected 类型和内嵌类型的全部成员 只有派生的类型可以访问该项
    internal 全部类型或成员 只能在包含它的程序集中访问该项
    private 类型和内嵌类型的全部成员 只能在它所属的类型中访问该项
    protected internal 类型和内嵌类型的全部成员 只能在包含它的程序集和派生类型的任何代码中访问该项
  3. C#常见的修饰符算法

    修饰符 应用于 说明
    new 函数成员 成员用相同的签名隐藏继承的成员
    static 全部成员 成员不做用于类的具体实例
    virtual 仅函数成员 成员能够由派生类重写
    abstract 仅函数成员 虚拟成员定义了成员的签名,但没有提供实现代码
    override 仅函数成员 成员重写了继承的虚拟或抽象成员
    sealed 类、方法和属性 对于类,不能继承自密封类。对于属性和方法,成员重写已继承的虚拟成员,但任何派生类中的任何成员都不能重写该成员。该修饰符必须与override一块儿使用
    extern 仅静态[DllImport]方法 成员在外部用另外一种语言实现
  4. 结构体编程

    • 结构是值类型,不是引用类型。
    • 存储在栈中或存储为内联(inline)(若是它们是存储在堆中的另外一个对象的一部分),其生存期的限制与简单的数据类型同样。
    • 结构体不支持继承。
    • 对于结构构造函数的工做方式有一些区别。尤为是编译器老是提供一个无参数的默认构造函数,它是不容许替换的。
    • 使用结构,能够指定字段如何在内存中的布局。
    • 注意,由于结构是值类型,因此new运算符与类和其余引用类型的工做方式不一样。new运算符并不分配堆中的内存,而是只调用相应的构造函数,根据传送给它的参数初始化全部的字段。
    • 结构遵循其余数据类型都遵循的规则:在使用前全部的元素都必须进行初始化。在结构上调用new运算符,或者给全部的字段分别赋值,结构就彻底初始化了。固然,若是结构定义为类的成员字段,在初始化包含的对象时,该结构会自动初始化为0。
    • 结构是会影响性能的值类型,但根据使用结构的方式,这种影响多是正面的,也多是负面的。正面的影响是为结构分配内存时,速度很是快,由于它们将内联或者保存在栈中。在结构超出了做用域被删除时,速度也很快。负面影响是,只要把结构做为参数来传递或者把一个结构赋予另外一个结构(如A=B,其 中A和B是结构),结构的全部内容就被复制,而对于类,则只复制引用。这样就会有性能损失,根据结构的大小,性能损失也不一样。注意,结构主要用于小的数据结构。但当把结构做为参数传递给方法时,应把它做为ref参数传递,以免性能损失————此时只传递告终构在内存中的地址,这样传递速度就与在类中的传递速度同样快了。但若是这样作,就必须注意被调用的方法能够改变结构的值。
    • 结构不是为继承设计的。这意味着:它不能从一个结构中继承。惟一的例外是对应的结构(和C#中的其余类型同样)最终派生于类System.Object。所以,结构也能够访问System.Object的方法。在结构中,甚至能够重写System.Object中的方法————如重写ToString()方法。结构的继承链是:每一个结构派生自System.ValueType类,System.ValueType类又派生自System.ObjectValueType并无给Object添加任何新成员,但提供了一些更适合结构的实现方式。注意,不能为结构提供其余基类,每一个结构都派生自ValueType
    • 为结构定义构造函数的方式与为类定义构造函数的方式相同,但 不容许定义无参数的构造函数。默认构造函数把数值字段都初始化为0,把引用类型字段初始化为null,且老是隐式地给出,即便提供了其余带参数的构造函数,也是如此。提供字段的初始值也不能绕过默认构造函数。
  5. 扩展方法数组

    • 扩展方法容许改变一个类,但不须要该类的源代码。因此使用扩展方法的情景之一是,当不知道类的源码或者不想修改该类的源码却想扩展该类,就能够用扩展方法。
    • 扩展方法是静态方法,它是类的一部分,但实际上没有放在类的源代码中。
    • 扩展方法需放在静态类中。
    • 对于扩展方法,第一个参数是要扩展的类型,它放在this关键字的后面。
    • 在扩展方法中,能够访问所扩展类型的全部共有方法和属性。
    • 若是扩展方法与类中的某个方法同名,就历来不会调用扩展方法。类中已有的任何实例方法优先。
  6. var关键字。编译器能够根据变量的初始化值“推断 ” 变量的类型。使用var关键字须要遵循的一些规则:安全

    • 变量必须初始化。不然,编译器就没有推断变量类型的依据。
    • 初始化器不能为空。
    • 初始化器必须放在表达式中。
    • 不能把初始化器设置为一个对象,除非在初始化器中建立了一个新对象。
  7. 密封类和密封方法数据结构

    • C#容许把类和方法声明为sealed。对于类,这表示不能继承该类;对于方法,这表示不能重写该方法。
    • 在把类或方法标记为sealed时,最可能的情形是:若是要对库、类或本身编写的其余类做用域以外的类或方法进行操做,则重写某些功能会致使代码混乱。也能够因商业缘由把类或方法标记为sealed,以防第三方以违反受权协议的方式扩展该类。但通常状况下,在把类或成员标记为sealed时要当心,由于这么作会严重限制它的使用方式。即便认为它不能对继承自一个类或重写类的某个成员发挥做用,仍有可能在未来的某个时刻,有人会遇到咱们没有预料到的情形,此时这么作就颇有用。.Net基类库大量使用了密封类 ,使但愿从这些类中派生出本身的类的第三方开发人员没法访问这些类。例如,string就是一个密封类。
  8. 约束ide

    • 泛型支持的几种约束类型:
    约束 说明
    where T : struct 对于结构约束,类型T必须是值类型
    where T : class 类约束指定类型T必须是引用类型
    where T : IFoo 指定类型T必须实现接口IFoo
    where T : Foo 指定类型T必须派生自基类Foo
    where T : new() 这是一个构造函数约束,指定类型T必须有一个默认构造函数
    where T1 : T2 这个约束也能够指定类型T1派生自泛型类型T2。该约束也称为裸类型约束
    • 只能为默认构造函数定义构造函数约束,不能为其余构造函数定义构造函数约束。
    • 在C#中,where子句的一个重要限制是,不能定义必须由泛型类型实现的运算符。运算符不能再借口中定义。在where子句中,只能定义基类、接口和默认构造函数。
  9. 复制数组函数

    • 若是数组的元素是值类型,调用Clone()方法就会复制全部值。如,int[] intArray1 = {1, 2}; int[] intArray2 = (int[])intArray1.Clone();其中intArray2数组的元素也变成了{1, 2}
    • 若是数组包含引用类型,则不复制元素,而只复制引用。
    • 除了使用Clone()方法以外,还可使用Array.Copy()方法建立浅表副本。
    • Clone()方法和Copy()方法有一个重要区别:Clone()方法会建立一个新数组,而Copy()方法必须传递阶数相同且有足够元素的已有数组。
    • 若是须要包含引用类型的数组的深层副本,就必须迭代数组并建立新对象。
  10. Array类使用QuickSort算法对数组中的元素进行排序。Array类中的Sort()方法须要数组中的元素实现IComparable接口。简单类型(如System.String和System.Int32)已经实现了IComparable接口。布局

  11. 元组

    • 数组合并了相同类型的对象,而元组合并了不一样类型的对象。
    • .NET 4定义了8个泛型Tuple类和一个静态Tuple类,不一样泛型Tuple类支持不一样数量的元素。例如,Tuple<T1>包含一个元素,Tuple<T1, T2>包含两个元素,以此类推。
    • 代码示例:

       1 public class TupleExample
       2 {
       3     static void Main()
       4     {
       5         TupleExample example = new TupleExample();
       6         var result = example.Divide(5, 2);
       7         Console.WriteLine("result of division: {0}, reminder: {1}", result.Item1, result.Item2);
       8     }
       9 
      10     public static Tuple<int, int> Divide(int dividend, int divisor)
      11     {
      12         int result = dividend / divisor;
      13         int reminder = dividend % divisor;
      14 
      15         return TupleExample.Create<int, int>(result, reminder);
      16     }
      17 }
      View Code

       

    • 若是元组包含项超过8个,就可使用带8个参数的Tuple类定义。最后一个模板参数是TRest,表示必须给它传递一个元组,这样就能够建立带任意个参数的元组了。示例:

      1 var tuple = Tuple.Create<string, string, string, int, int, int, double, Tuple<int, int>>("Stephanie", "Alina", "Nagel", 2009, 6, 2, 1.37, Tuple.Create<int, int>(52, 3490));
      View Code

       

  12. 运算符

    • is运算符:能够检查对象是否与特定的类型兼容。“兼容”表示对象是该类型或者派生自该类型。
    • as运算符:用于执行引用类型的显示类型转换。若是要转换的类型与制定的类型兼容,转换就会成功进行;若是类型不兼容,as运算符就会返回null值。
    • sizeof运算符:使用该运算符能够肯定栈中值类型须要的长度(单位是字节);若是对于复杂类型(和非基元类型)使用该运算符,就须要把代码写在unsafe块中,如:unsafe{Console.WriteLine(sizeof(Customer));}
    • 可空类型和运算符:一般可空类型与一元或二元运算符一块儿使用时,若是其中一个操做数或两个操做数都是null,其结果就是null。如: 
      int? a = null; 
      int? b = a + 4; // b = null 
      int? c = a * 5; // c = null
    • 空合并运算符(??):该运算符提供了一种快捷方式,能够在处理可空类型和引用类型时表示null可能的值。这个运算符放在两个操做数之间,第一个操做数必须是一个可空类型或者引用类型;第二个操做数必须与第一个操做数的类型相同,或者能够隐含地转换为第一个操做数的类型。
  13. 比较引用类型的相等性

    • ReferenceEquals()方法:该方法是一个静态方法,测试两个引用是否引用类的同一个实例,特别是两个引用是否包含内存中的相同地址。若是提供的两个引用引用同一个对象实例,则返回true,不然返回false。可是它认为null等于null。另外,该方法在应用于值类型时,它老是返回false,由于为了调用这个方法,值类型须要装箱到对象中。
    • 虚拟的Equals()方法:Equals()虚拟版本的System.Object实现代码也能够比较引用。但由于这个方法是虚拟的,因此能够在本身的类中重写它,从而按值来比较对象。特别是若是但愿类的实例用做字典中的键,就须要重写这个方法,以比较相关值。不然,根据重写Object.GetHashCode()的方式,包含对象的字典类要么不工做,要么工做的效率很是低。在重写Equals()方法时要注意,重写的代码不会抛出异常。同理,这是由于若是抛出异常,字典类就会出问题,一些在内部调用这个方法的.NET基类也可能出问题。
    • 静态的Equals()方法:Equals()的静态版本与其虚拟实例版本的做用相同,其区别是静态版本带有两个参数,并对它们进行相等比较。这个方法能够处理两个对象中有一个是null的状况,所以,若是一个对象多是null,这个方法就能够抛出异常,提供额外保护。静态重载版本首先要检查它传递的引用是否为null。若是他们都是null,就返回true(由于nullnull相等)。若是只有一个引用是null,就返回false。若是两个引用实际上引用了某个对象,它就调用Equals()的虚拟实例版本。这表示在重写Equals()的实例版本时,其效果至关于也重写了静态版本。
    • 比较运算符(==):最好将比较运算符看做严格的值比较和严格的引用比较之间的中间选项。在大多数状况下,下面的代码表示正在比较引用:bool b = (x == y);// x, y object references
  14. 运算符重载

    • 运算符重载的声明方式与方法相同,但operator关键字告诉编译器,它其实是一个自定义的运算符重载,后面是相关运算符的实际符号,返回类型是在使用这个运算符时得到的类型。
    • 对于二元运算符(它带两个参数),如+-运算符,第一个参数是运算符左边的值,第二个参数是运算符右边的值。
    • 通常把运算符左边的参数命名为lhs,运算符右边的参数命名为rhs
    • C#要求全部的运算符重载都声明为publicstatic,这表示它们与它们的类或结构相关联,而不是与某个特定实例相关联,因此运算符重载的代码体不能访问非静态类成员,也不能访问this标识符。
    • C#语言要求成对重载比较运算符。即,若是重载了==,也就必须重载!=;不然会产生编译错误。另外,比较运算符必须返回布尔类型的值。这是它们与算术运算符的根本区别。
    • 在重载==!=时,还必须重载从System.Object中继承的Equals()和GetHashCode()方法,不然会产生一个编译警告。缘由是Equals()方法应实现与==运算符相同类型的相等逻辑。
  15. 委托

    • 理解委托的一个要点是它们的类型安全性很是高。
    • 理解委托的一种好方式是把委托看成这样一件事,它给方法的签名和返回类型指定名称。
    • Action 
      • Action是无返回值的泛型委托。
      • Action表示无参,无返回值的委托
      • Action<int,string>表示有传入参数int,string无返回值的委托
      • Action<int,string,bool>表示有传入参数int,string,bool无返回值的委托
      • Action<int,int,int,int>表示有传入4个int型参数,无返回值的委托
      • Action至少0个参数,至多16个参数,无返回值。
    • Func 
      • Func是有返回值的泛型委托
      • Func<int>表示无参,返回值为int的委托
      • Func<object,string,int>表示传入参数为objectstring返回值为int的委托
      • Func<object,string,int>表示传入参数为objectstring返回值为int的委托
      • Func<T1,T2,,T3,int>表示传入参数为T1,T2,T3(泛型)返回值为int的委托
      • Func至少0个参数,至多16个参数,根据返回值泛型返回。必须有返回值,不可void
  16. Lambda表达式

    • 只要有委托参数类型的地方,就可使用Lambda表达式。或者说Lambda表达式能够用于类型是一个委托的任意地方。
    • 若是只有一个参数,只写出参数名就足够了。若是委托使用多个参数,就把参数名放在小括号中。为了方即可以在小括号中给变量添加参数类型。
    • 若是Lambda表达式只有一条语句,在方法块内就不须要花括号和return语句,由于编译器会添加一条隐式return语句。
  17. 正则表达式

    • 经常使用的特定字符和转义序列以下表:
    符 号 含 义 示 例 匹配的示例
    ^ 输入文本的开头 ^B B,但只能是文本中的第一个字符
    $ 输入文本的结尾 X$ X,但只能是文本中的最后一个字符
    . 除了换行符(\n)之外的全部单个字符 i.ation isation、ization
    * 能够重复0次或屡次的前导字符 ra*t rt、rat、raat和raaat等
    + 能够重复1次或屡次的前导字符 ra+t rat、raat和raaat等(但不能是rt)
    ? 能够重复0次或1次的前导字符 ra?t 只有rt和rat匹配
    \s 任何空白字符 \sa [space]a、\ta、\na(其中[space]表示空格,\t和\n都是转移字符)
    \S 任何不是空白的字符 \SF aF、rF、cF,但不能是\tF
    \b 字边界 ion\b 以ion结尾的任何字
    \B 不是字边界的任意位置 \BX\B 字中间的任何X
    • 能够把替换的字符放在方括号中,请求匹配包含这些字符。例如,[1|c]表示字符能够是1c。在方括号中,也能够指定一个范围,例如[a-z]表示全部的小写字母,[A-E]表示A~E之间的全部大写字母(包括字母AE),[0-9]表示一个数字。若是要搜索一个整数,就能够编写[0-9]+
  18. 集合

    • 链表。LinkedList<T>是一个双向链表,其元素指向它前面和后面的元素。其特色是:插入快,查找慢。
    • 有序列表。若是须要基于键对所需集合排序,就可使用SortedList<TKey,TValue>类,这个类按照键给元素排序。
    • 字典。 
      • 字典的主要特征是能根据键快速查找值。也能够自由添加和删除元素,这点有点像List<T>类,但没有在内存中移动后续元素的性能开销。
      • 用做字典中键的类型必须重写Object类的GetHashCode()方法。只要字典类须要肯定元素的位置,它就要调用GetHashCode()方法。GetHashCode()方法返回的int由字典用于计算在对应位置放置元素的索引。
      • 字典的性能取决于GetHashCode()方法的实现代码。
      • 除了实现GetHashCode()方法以外,键类型还必须实现IEquatable<T>.Equals()方法,或重写Object类的Equals()方法。由于不一样的键对象可能返回相同的散列代码,因此字典使用Equals()方法来比较键。
  19. GetHashCode()方法的实现代码必须知足以下要求:

    • 相同的对象应老是返回相同的值。
    • 不一样的对象能够返回相同的值。
    • 它应执行得比较快,计算的开销不大。
    • 它不能抛出异常。
    • 它应至少使用一个实例字段。
    • 散列代码值应平均分布在int能够存储的整个数字范围上。
    • 散列代码最好在对象的生存期中不发生变化。
  20. 若是为Equals()方法提供了重写版本,但没有提供GetHashCode()方法的重写版本,C#编译器就会显示一个编译警告。

  21. LINQ

    • 查询表达式必须以from子句开头,以selectgroup子句结束。在这两个子句之间,可使用whereorderbyjoinlet和其余from子句。
    • LINQIEnumerable<T>接口提供了各类扩展方法,以便用户在实现了该接口的任意集合上使用LINQ查询。
  22. 释放非托管的资源

    • 在定义一个类时,可使用两种机制来自动释放非托管的资源。这些机制经常放在一块儿实现,由于每种机制都为问题提供了略微不一样的解决办法。
    • 释放非托管资源的两种机制:声明一个析构函数(或终结器);在类中实现System.IDisposable接口。
  23. 析构函数

    • 在销毁C++对象时,其析构函数会当即运行。但因为使用C#时垃圾回收器的工做方式,没法肯定C#对象的析构函数合适执行。因此,不能在析构函数中放置须要在某一时刻运行的代码,也不该使用能以任意顺序对不一样类的实例调用的析构函数。若是对象占用了宝贵而重要的资源,应尽快释放这些资源,此时就不能等待垃圾回收器来释放了。
    • C#析构函数的实现会延迟对象最终从内存中删除的时间。没有析构函数的对象会在垃圾回收器的一次处理中从内存中删除,但有析构函数的对象须要两次处理才能销毁:第一次调用析构函数时,没有删除对象,第二次调用才真正删除对象。另外,运行库使用一个线程来执行全部对象的Finalize()方法。若是频繁使用析构函数,并且使用它们执行长时间的清理任务,对性能的影响就会很是显著。
  24. IDisposable接口

    • 在C#中,推荐使用System.IDisposable接口替代析构函数。IDisposable接口定义了一种模式(具备语言级的支持),该模式为释放非托管的资源提供了肯定的机制,并避免产生析构函数固有的与垃圾回收器相关的问题。IDisposable接口声明了一个Dispos()方法,它不带参数,返回void
    • Dispose()方法的实现代码显式地释放由对象直接使用的全部非托管资源,并在全部也实现IDisposable接口的封装对象上调用Dispose()方法。这样,Dispose()方法为什么时释放非托管资源提供了精确的控制。

以上是《C#高级编程》前二十章的读书笔记。笔记摘录了笔者认为易忘的知识点,方便之后查阅和复习。摘抄整理过程当中不免疏忽和遗漏,若有错误不当之处,请不吝指出,在此感激涕零!


声明:本文欢迎转载和分享,可是请尊重做者的劳动成果,转载分享时请注明出处:http://www.cnblogs.com/davidsheh/p/5236686.html 。同时,码字实在不易,若是你以为笔者分享的笔记对你有点用处,请顺手点击下方的推荐,谢谢!

相关文章
相关标签/搜索