【CLR VIA C#】读书笔记

 

工作几年了才看,记录下笔记备忘。

 

章节                

笔记                

1.CLR的执行模型                

  1. 公共语言运行时(Common Language Runtime,CLR)                        

  2. 源代码-->编译器检查语法和分析源代码-->托管模块(manged module)(含IL代码)-->运行时有JIT编译器生成cpu指令执行                        

  3. 托管模块由PE32|PE32+(64位)、CLR头、元数据、IL代码(中间语言运行时转换成CPU指令) 组成                        

  4. COM组件是一种规范,实现规范的DLL也可称为COM组件 。                     

  5. 托管模块中的元数据描述了模块中定义了什么以及引用了什么。                        

  6. 程序集:由托管模块、资源文件被编译器生成,包含一个mainfest清单的“组件”,可以是执行程序或DLL。                        

  7. 指定32位或anycpu时均可在64(64位系统中32位程序则在WOW64下执行)位下运行,可以在Environment的Is64BitOperatingSystem判断是否为64位操作系统                        

  8. IL是与CPU无关的机器语言,可视为面向对象的机器语言                        

  9. 有JITter(JIT编译器)(just in time)将IL即时编译成cpu指令(不同cpu生成对应指令),只有方法第一次执行时JIT编译器才会编译                        

  10. vs中生成-->高级-->调试信息 选择 full则可在vs中执行即时调试功能,选择none则不生成pdb文件,选择only则不共享生成代码,pdb位源码与IL的映射                        

  11. NGen.exe可以将IL代码提前编译成本机代码保存到磁盘文件中,避免运行时进行编译,但是没有做jit编译器所做的高度优化                        

  12. 将IL编译成CPU指令时,CLR执行验证(verification)过程,以检验程序的健壮性和安全性。                        

  13. 可以用unsafe关键字标记不安全的代码,CLR验证过程会跳过不安全代码。                        

  14. NGen.exe可提高程序启动速度,减少应用程序的工作集,缺点是没有防反编译、可能失去同步、代码未根据cpu优化                        

  15. Framework类库(Framework Class Library,FCL)                        

  16. WCF :Windows Communication Foundation                        

  17. 通用类型系统:Common Type System,CTS                        

  18. c#类只能单集成,接口可以多继承                        

  19. ==在引用情况下比较的是引用地址,equals一直比较值                        

  20. 公共语言规范:(Common Language Specification,CLS),实现通过com组件跨语言访问的规范,是CLR下各个编程语言的子集。可标记[assembly:CLSCompliant(true)]特性进行检查是否符合CLS。                        

2.生成、打包、部署和管理应用程序及类型                

  1. 使用CSC.exe编译c#代码生成exe,可以通过 @响应文件名 来实现加载配置文件,默认全局CSC.rsp配置在.net文件夹下面                        

  2. 元数据包含:定义表、引用表、清单表                        

  3. 程序集特点:定义了可重用的类型、用一个版本号标记、可以关联安全信息。                        

  4. CLR只有在调用的方法确实引用了未加载程序集中的类型时,才会加载程序集。                        

  5. 程序集链接器:AL.EXE 可以实现生成包含不同编译器生成的模块,或者在生成时不清楚程序集的打包要求的模块,可以添加资源文件                        

  6. 程序集版本资源信息:在AssemblyInfo.cs中设置,可通过右键属性查看                        

  7. 版本号规则:使用4个句点分割:主版本号.次版本号.内部版本号(每天编译递增).修订号(每天编译次数递增)                        

  8. AssemblyFileVersion:给用户看的版本号 AssemblyInformationaVersion:调用此程序集的产品的版本号 AssemblyVersion:CLR检查的版本号                        

  9. 附属程序集:标记了语言文化的程序集,可用AL.exe生成                        

  10. 私有部署的程序集:在应用程序基目录或者子目录部署的程序集                        

  11. 指定程序集查找目录:在配置文件中增加probing 节点甚至privatePath路径,可以以分号分割,只能是子目录。winfrom为exe.config webfrom为web.config                        

  12. 机器.NET设置文件名为Machine.config在系统.net文件夹下面可找到                        

3.共享程序集和强命名程序集                

  1. 弱命名程序集:普通程序集 强命名程序集:使用发布者的公钥、私钥进行签名,进行唯一性标识                        

  2. 弱命名可以私有部署、不能全局部署,强命名可以私有部署、可以全局部署                        

  3. 程序集唯一标识:文件名(无扩展名)、版本号、语言文化和公钥(公钥标记public key token)                        

  4. 使用SN.EXE创建公钥/私钥对,然后使用csc命令选择/keyfile 及类 来编译一个强命名程序集                        

  5. 全局程序集缓存(Global Assembly Cache,GAC):由多个应用程序访问的程序集存放的公认目录,一般在 系统/.net/assembly下,可以用GACUitl.exe进行程序集安装                        

  6. GAC只能安装强命名程序集,一般不推荐安装到GAC,如果安装则应该使用windows installer(MSI)进行安装                        

  7. CSX.EXE查找目录:1.工作目录 2.CSX所在目录,含CLR的各种DLL文件 3.使用/l编译器指定任何目录 4.使用LIB环境变量指定的任何目录                        

  8. 安装.net framework实际安装两套copy,一个安装在GAC(存在多个copy以便针对不同CPU优化),一个安装在CLR用于编译(不含IL代码,所以不依赖机器CPU)                        

  9. 强命名先使用私钥对程序集部分信息进行加密计算HASH,然后公钥写入程序集,最后再导入到GAC时用公钥解密结果与GAC计算结比较                        

  10. 如果从GAC之外加载强命名程序集,则CLR会在程序集加载后比较哈希值,保证每次程序集文件没有被篡改,GAC优先根据CPU类型搜索。                        

  11. 可以选择延迟签名,进行只放公钥及预留哈希存储空间,程序混淆的话必须用延迟签名才能澳证hash正确                        

  12. 配置文件的CODEBASE可以标记一个URL,CLR会自动下载文件,自动对比时间戳                        

  13. CLR解析类型引用:相同文件已经早期绑定直接加载,不同文件相同程序集,查找FILEDEF表,加载清单,不同文件不同程序集,加载被引用程序集的清单文件                        

  14. 配置文件可实现根据public key token、语言、版本号来替换一个DLL的功能                        

  15. 发布者策略控制:为程序集指定配置文件,指定更新版本
                                                   

4.类型基础                

  1. 基类object包含MemberwiseClone(创建新实例)、Finalize(重写以实现垃圾回收前的操作)                        

  2. new一个对象时会计算所有类型直到object类型的成员所占的空间,然后传递参数后返回引用地址到对象                        

  3. is检查对象是否兼容于指定类型,as直接转换对象,两者好处位均不抛出异常,失败返回false和null。                        

  4. 引用类型重名时可以使用using a=system.IO;如果命名空间也重名则可用外部别名方式区分。                        

  5. CLR创建一个进程,进程可能有多个线程,线程创建时会分配到1MB的栈,按顺序执行,每个方法调用都会在调用栈中创建并压入一个栈帧,调用后释放栈帧。                        

  6. 堆上所有对象都包含:类型对象指针(GetType方法)和同步块索引,当CLR确认方法所需要的所有类型对象都已创建,方法的代码已经编译之后,就允许线程执行本机代码了。                        

  7. 栈中最简单的方法包含序幕(初始化为局部变量分配内存位null或0)和尾声(垃圾回收以便返回至调用者)                        

  8. 调用静态方法时CLR会定位与定义静态方法的类型对应的类型对象,然后JIT编译器在类型对象方法表中查找与被调用方法对应的记录项                        

  9. 调用非虚实例方法时JIT会找到与 发出调用的那个变量的类型 对应的类型对象,会向上找到基类,然后JIT查找方法表                        

  10. 虚方法调用先检查发出调用的变量,并更随地址来到发出调用的对象,然后检查“类型对象指针”成员,然后查找方法表
                                                   

5.基元类型、引用类型和值类型                

  1. 基元类型:编译器直接支持的数据类型,和System.Int32是属于映射关系,如int、byte、float、double、decimal                        

  2. 不会发生数据丢失的情况下,c#允许隐式转换,使用关键字checked和unchecked可以检查是否溢出。                        

  3. 引用类型有托管堆分配,new后返回对象内存地址,值类型一般在线程栈上分配(会初始化0),类是引用类型,结构或枚举是值类型都继承自System.ValueType,引用类型传指针地址,值类型新建对象。                        

  4. 将值类型转换成引用类型就是装箱,在托管堆中分配内存(浪费资源),返回引用地址。将引用类型转换成值类型就是拆箱(只能转为原本值类型)。                        

  5. 检查同一性用ReferenceEquals,Equals默认实现同一性。                        

  6. dynamic在c#编译器生成payload(有效载荷),在运行时根据对象实际类型判断要执行什么操作。使用了称为运行绑定器得嘞。                        

6.类型和成员                

  1. 类型默认访问性为internal,可以指定友元程序集来实现程序集访问限制。                        

  2. private、protected、internal、protected internal、public,派生类型重写后的可访问性要和重写成员具有相同可访问性。                        

  3. 静态类只能定义静态成员。                        

  4. partial关键字告诉c#编译器,类、结构、接口的定义源代码可能要分散到一个或多个源文件中。CLR对该功能一无所知,完全有编译器实现。                        

  5. 组件软件编程(Component Software Programming,CSP)                        

  6. call可调用静态方法、实例方法和虚方法,callvirt可调用实例方法和虚方法,不能调用静态方法,会检查对象是否为null,会递归查找调用对象。                        

  7. 应减少虚方法的使用,1是慢2是不能内嵌3版本控制脆弱4定义基类时要提供简单重载方法                        

  8. 应当对类使用sealed、internal关键字,对字段使用private关键字,然后考虑protected或internal最后考虑virtual。                        

  9. new关键字用在方法上时,是告诉编译器新方法和基类无任何关系,编译器将认为是此类上定义的新方法。override则是认为重写基类方法。
                                                   

7.常量和字段                

  1. 常量const是值从不变化的符号,值会在编译时确定并保存为程序基元数据,基元类型均可定义为常量。常量值直接嵌入代码,不为常量分配内存。隐式为static。                        

  2. 静态字段实在第一次调用类是JIT编译时分配内存,实例字段则是在构造类型的实例时分配。readonly只能在构造函数里面赋值。                        

  3. readonly是运行时能赋值,常量则是一直固定的值。readonly不可改变的是引用,而非字段引用的对象。
                                                   

8.方法                

  1. 引用类型:构造器是将类型的实例初始化为良好状态的特殊方法.ctor。创建引用类型时先为字段分配内存(默认为0或null),然后初始化附加字段类型对象指针和同步块索引,最后调用类的实例构造器来设置初始状态。                        

  2. 静态类不会生成默认构造器,是抽象密封类。构造器中不能调用虚方法。每个重载的构造器都会默认生成所有字段的值,所以通过this()调用其他构造器能减少IL代码量。                        

  3. 值类型:编译器不会生成和调用默认构造器,除非显式调用。字段总是被初始化为0或null。在值类型中this=new x();是允许赋值的,引用类型不行。new后再会将所有字段赋值为0;                        

  4. new是创建实例,并不是调用默认构造器。                        

  5. 使用static可以创建类型构造器,类型构造器永远没有参数,总是私有的,会在类型首次访问时执行。“.cctor”                        

  6. CLR规范要求操作符重载方法必须是public和static方法 如 operator+。                        

  7. 转换操作符试讲对象从一种类型转换成另一种类型的方法,必须是public和static方法。格式:方法加implicit operator 类名 是隐式转换,explicit operator 类名 是显式转换。                        

  8. c#扩展方法(第一个且只有第一个参数前有this),不支持扩展属性、事件等,必须在非反省的静态类中声明,类名没有限制,不能套在别的类,同时扩展了派生类。                        

  9. 分部类中可以声明分部方法,分部类是密封的,关键词partial.分部方法返回类型始终是void,且被视为private,并且参数不能有out。                        

9.参数                

  1. 可为方法、构造器和有参属性指定默认值,有默认值的参数必须放在没有默认值的参数后面,默认值必须是编译时能确定的常量值。default和new都可以创建值类型实例。                        

  2. var不能用来声明方法的参数类型和声明类型中的字段,var只能声明方法内部的局部变量。                        

  3. 默认情况方法参数引用类型传指针,值类型传实例副本。ref和out区别在于由哪个方法负责初始化对象。传递引用方式给方法的变量类型必须与方法声明的参数类型相同。                        

  4. params关键字可实现可变数量的参数,只能为最后一个参数。 对性能有影响,可参考System.String的Concat方法设计。                        

  5. 声明方法的参数类型时,应指定最弱的类型,宁愿要接口也不要基类。如选IList而非List作为参数类型,方便扩展。一般最好将方法返回类型声明为最强类型。                                               

10.属性                

  1. 字段应设置为private,封装了字段访问的方法通常称为访问器。在属性中私有字段称为支持字段。声明属性而扛get、set方法的实现,c#会自动声明一个私有字段,自动实现的属性。                        

  2. 在任何想要序列化或反序列化的类型中,都不要用自动属性,因为每次生成的私有字段名称不固定。                        

  3. 属性不能作为out或ref参数,尽量减少属性使用。                        

  4. 初始化对象class a=new class{p=1}.toString();集合初始化:list={"a","b"},必须支持add方法,继承Ienumerable就是集合(实现集合访问器)。                        

  5. 匿名类型:1.var o1=new {name="jeff",Year=1964} 2.var o2=new {Name,dt.Year}属性名不变,匿名类型不能作为参数,属性是只读的。                        

  6. System.Tuple组元类型,Tuple<int, int> p2 = new Tuple<int, int>(10, 20); 可以实现返回多个类型结果的类型,替换ref/out。只读,属性名称为item{0},最多支持8个参数可以嵌套。                        

  7.  使用this来实现索引器,不支持静态索引器,也称为有参属性。如果有Item属性则就会支持索引器。可以定义多个参数集不同的索引器。JIT会将索引器方法编译在调用的方法中来提升性能。                

  8. 属性可以单独为get、set设置不同访问权限。                        

11.事件                

  1. 第一步事件传递参数应该从EventArgs派生,并且以EventArgs做为类名结尾。不需要附加消息时可以直接使用EventArgs.Empty。

  2. 第二步定义事件成员:"修饰符 event 委托类型 事件名称",事件不应有返回值。

  3. 第三步骤定义通知事件的登记对象,设置为虚方法onEvent,为了线程安全则需先将事件复制到临时变量(Volatile.Read(ref <eventName>)),然后判断临时变量是否为null,不为null则调用事件通知。

  4. 第四步执行事件,构造对象,然后调用登记对象onEvent(参数)。

  5. 编译器实现事件:1定义初始化为null的私有委托字段 2.一个公共add方法(Delegate.Combine连接调用列表),以线程安全方式向事件添加委托 3.一个remove方法来注销事件关注

  6. 创建委托实例方法(sender,e)sender为调用事件的对象,方便回传操作,e为事件传递参数对象,+=相当于调用add,-=相当于调用remove。

  7. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class TestEvent
      {
          public delegate void RunHandle<Teventargs>(object sender, Teventargs e);
          public event RunHandle<RunEventargs> RunEvent;
          protected virtual void OnRun(RunEventargs e)
          {
     
              var tempEvent = RunEvent;
              if (tempEvent != null)
              {
                  tempEvent(this, e);
              }
          }
     
          public void Go(string name)
          {
              OnRun(new RunEventargs() { Name = name });
          }
     
      }
      public class RunEventargs : EventArgs
      {
          public string Name { getset; }
      }
12.泛型
  1. 不允许创建泛型枚举类型,允许创建泛型接口和泛型委托、泛型引用、值类型,以及泛型方法。以大写T开头代表泛型。T为类型参数,调用时指定的类型为类型实参。

  2. 泛型好处:源代码保护、类型安全、代码清晰、更好的性能。常用泛型接口在System.Collections.Generic命名空间。

  3. 具有泛型类型参数的类型称为开放类型,指定了泛型类型实参的类型就称为封闭类型。泛型类中静态对象是根据封闭类型来区分。

  4. 指定泛型类型实参不影响继承层次结构。

  5. 使用 using=类型 来简化语法引用泛型封闭类型。

  6. 代码爆炸:CLR要为每种不同的方法、类型组合生成代码。优化:相同类型编译一次,视引用类型实参完全相同来共享代码(都是查找指针),值类型则每次都要编译。

  7. 最好使用FCL预定义的泛型Action和Func来实现泛型委托。

  8. 协变性是指返回类型的兼容性,使用out标记,输出参数可以从一个类型改为他的某个基类;逆变性是指参数的兼容性,用in标记,输入参数可以改为他的某个派生类。都无法使用ref、out标记参数。

  9. 调用泛型方法时可以省略<type>,编译器可自行推断。如果方法和泛型方法重则,则优先考虑比较明确的匹配,再考虑泛型匹配。

  10. 可以为泛型添加约束:where T:IComparable<T>{} ,重写虚泛型发发时,重写的方法必须指定相同数量的类型参数,且会继承基类约束,不允许重写约束。

  11. 类型参数可以指定0个或一个主要约束:要么是与约束类型相同的类型,要么是从约束类型派生的类型,如(where T:class)。特殊:class代表引用类型约束、struct代表值类型约束。

  12. 类型参数可以指定0个或多个次要约束:代表接口类型,要实现所有接口。类型参数约束:指定的参数要么就是约束类型,要么是约束类型的派生类,如(method<T,TBase>() where T:TBase)。

  13. 类型参数可以指定0个或一个构造器约束:承诺类型实参是实现了公共无参构造器的非抽象类型,如(where T:new()),目前只支持无参构造器。

  14. 使用as语法进行类型转换;使用default(T)设置默认值(引用类型设置为null值类型为0);==或!=null都可以执行,不过值类型==null永远为false;如果参数不能肯定是引用类型,则不能用==比较同类型变量;泛型无法应用基元操作符计算。

13.接口
  1. CLR类只能有一个基类,可以有继承多个接口,凡是使用基类型实例的地方,都能使用派生类型的实例。

  2. 接口是对一组方法签名进行了统一命名,接口还能定义事件、无参属性和有参属性(索引器),接口不能定义任何构造器方法,也不能定义任何实例字段。

  3. 使用interface关键字,可以使用访问修饰符,可以从另一个接口继承,可定义泛型接口。

  4. 接口方法标记为public、sealed,所以派生类不能重写,但是派生类可以再次继承相同接口,使用new关键字重写接口方法。

  5. 实现了接口的对象,可以赋值给其实现任意接口类型。所有类都隐式继承Object类。

  6. 在c#中,将定义方法的那个接口的名称作为方法名前缀(void IDisposable.Dispose()),就会创建“显式接口方法实现(EIMI)”,不能指定访问修饰符默认为私有,只能通过接口变量调用,且不能被重写。

  7. 泛型接口好处:类型安全、处理值类型时装箱次数少很多、类型可以实现同一个接口若干次,只要每次使用不同的类型参数。接口的泛型类型参数可标记为逆变和协变。

  8. 将泛型类型参数约束为接口的好处:可约束为多个接口,那么参数必须全部实现;传递值类型减少装箱;

  9. 如果继承的多个接口具有相同的方法签名,那么必须使用显式接口方法实现,并通过接口实例来调用。

  10. 通过显示接口实现来实现接口方法,然后在类中再定义一个相同签名方法但参数非object的方法,以此来实现类型安全,减少装箱。

  11. 应避免使用EIMI:1.没有文档解释如何实现一个EIMI方法 2.值类型的实例在转换成接口时装箱 3.EIMI不能有派生类型调用

  12. 基类还是接口:1.is-a杜比can-do,如果是can-do关系则使用接口,否则使用基类;2.易用性、一致性、版本控制基类比较好;类能同时有基类和多个接口。

14.字符、字符串和文本处理
  1. 字符总是表示成16位Unicode代码值,可以使用强制类型转换、convert、Iconvertible来实现数值类型和char实例的转换。

  2. 使用Environment.NewLine来实现构造换行字符串。字符串前加@符号使编译器知道这是逐字字符串,不进行转义。

  3. 字符串是不可变的,排序时要区分大小写,忽略语言文化,ToUpperOrdinal对文化不敏感。应避免使用==和!=来比较字符串因为看不出比较方式。

  4. 如果经常对字符串进行区分大小写的序号比较,或者事先知道许多字符串对象都有相同的值,就可以利用CLR的字符串留用机制。string.Intern或string.isintern。通过Object.ReferenceEquals判断引用是否相同。

  5. 字符串池:编译器将相同字符串的多个实例合并成一个实例,能显著较少模块的大小。一般字符串的方法都是返回的一个新字符串对象。

  6. StringBuilder包含一个Char数组构成的字段,如果内存不够则自动增加。最大容量默认Int32最大值。可实现分配容量大小,超过则翻翻。在长度超过容量和调用Tostring方法时会创建新的对象。

  7. 自定义类应重写Tostring方法,然后实现IFormattable接口来选择格式和语言文化。通过Parse来解析字符串并获取对象。

  8. 不显示指定类型默认为UTF-8,UFT-16即为Unicode编码。Encoding实现转码,BitConverter.Tostring实现显式字节值。不鼓励使用default编码,因为会根据机器而改变。通过GetDecoder方法可存储一般的字节。

  9. 在System.Convert下可转换base-64编码和解码。使用System.Security.SecureString来构造一个在内存中加密的字符串,保证其他程序无法通过扫描内存得到机密数据。

15.枚举类型和位标志
  1. 枚举类型是值类型,枚举类型不能定义任何方法、属性和事件,但可以使用扩展方法模拟定义事件。枚举类型可为byte、short、int、long等基元类型。

  2. 通过GetName来获取枚举名称,通过Parse来根据名称获取枚举值。可以用IsDefined来判断数值对于枚举类型是否合法(参数校验,尽量少用)。枚举应该和需要他的类同级。

  3. 枚举类型表示单个数值,而位标志表示位集合。应显示赋值位标志枚举,需在枚举定义上增加Flages特性。格式化“G”常规“D”十进制“X”十六进制。

16.数组
  1. 数组是将多个数据项作为集合来处理的机制,始终继承array和object所以为引用类型。

  2. 初始化数组:string[] names=new string[]{"1","2"};var names=new string[]{"1","2"};var names=new[]{"1","2",null};string names={"1","2"};

  3. 引用类型数组可以根据基类相互转换,值类型不能转换只能通过Array.Copy进行浅赋值(部分copy可选择BlockCopy)。

  4. 可以手动创建下限非0的数组,CreatInstance。可以使用unsafe关键字来创建不安全数据访问方法,实现直接访问内存操作数据。

  5. 可以使用stackalloc来创建高性能的在线程栈上分配的数组。主要用于和非托管代码交互。

17.委托
  1. 委托在IL中有4个方法构成:构造器、Invoke、BeginInvoke、EndInvoke。

  2. 委托链是委托对象的集合,可以调用集合中的委托所代表的全部方法。可以使用Delegate.Combine[+=]方法将委托添加到委托链中(含Remove(-=)方法)。

  3. 委托链是顺序调用所有方法,除了最后一个值,其他返回值都会被抛弃,如果中间一个卡住则全部卡住。可以使用GetInvocationList显式调用委托方法。

  4. 每次相同输入返回相同称为可靠性,没有自动释放内存类称为健壮性。

  5. 建议使用Action委托和Func委托,如果使用ref、out或params则要自己定义委托。

  6. 委托简化语法:1.不需要构造对象 2.lambda 不需要回调方法 3.局部变量不需要手动包装到类中即可传给回调方法。

  7. 可以使用CreateDelegate方法,通过反射允许在编译时不直到委托的所有必要信息的前提下创建委托。DynamicInvoke来调用委托回调方法。

18.定制特性
  1. 定制特性允许为几乎每一个元数据表记录项定义和应用信息。特性就是将一些附加信息与某个目标元素关联起来方式。

  2. DllImport特性:告诉clr该方法的实现位于指定DLL的非托管代码中。Serializable:标记为可序列化。Flag:使枚举类型成为位标志集合。

  3. 定制特性继承自Attribute,特性是类的实例,类必须有构造器才能创造实例,所以特性应用时类似调用类的构造器。一个目标元素可应用多个特性。

  4. 自定义特性时可以使用AttributeUsageAttribut来指定特性的应用范围,及可指定同一特性同一目标应用次数。还可用Inherited指定特性是否能继承。

  5. 可以使用type()或反射对象的或Attribute类的IsDefined、GetCustomAttributes、GetCustomAttribute方法来检测是标记特性,以及判断特性的类型。

  6. 可以不从Attribute派生来创建特性。可以使用System.Diagnostics.ConditionalAttribute来实现根据类头定义的#biaozhi 来决定是否加载特性。

19.可空值类型
  1. 使用System.Nullable<T>来定义可空值类型,Nullable<Int32> x=5; 或者 Int32? x=5;

  2. 一般在可空类型运行操作符时,如果参数值为null则结果为null。个别二元操作除外。

  3. 空结合操作符:"??",如果左边不为null则返回这个操作数的值,如果为null就返回右边的值。int32 x=b??123;

  4. 可空类型当参数时,只有在不为null情况下才会装箱。Gettype得到的时非nullable类型。

20.异常和状态管理
  1. 同时存在finally和return时,finally总是会执行的。

  2. 应将最具体的捕捉类型放在上面,接着是他们的基类型,最后是exception,和未指定类型(能捕捉非Exception派生的类型,即合cls不相容的类型)。

  3. 底层catch,未捕捉到异常则通过调用堆栈找到上层catch,如果找到则执行底层finally,最后执行上层catch代码。

  4. 捕捉到异常后可以:重新抛出给上层、丰富异常信息后给上层、过滤掉

  5. 终止线程或卸载appdomain会造成CLR抛出threadabortexception,使finally能够执行。一般处理资源清理工作,如释放对象,关闭数据流。

  6. catch和finally中的代码应该只有1、2行,要避免自身抛出异常。如果在其中抛出异常则上一个异常信息将会丢失,会按流程处理本次异常。

  7. 使用异常对象的stacktrace属性查看堆栈跟踪,描述异常发生前调用了哪些方法。可以使用System.Diagnostics.StackTrace类型查看构成堆栈跟踪的栈帧。

  8. 可以定义自己的异常类,继承exception后增加字段,以及重写Message。

  9. 执行catch或finally是Clr不允许终止线程,所以可放入简单的状态修改代码。使用AppDomain.Unload来卸载整个AppDomain来初始化状态,使用Environment.FailFast方法来终止进程。

  10. 使用lock\using\foreach\析构器时C#编译器会自动生成try\finally方法块,来释放资源。

  11. 一般不要捕捉exception异常,除非会重新抛出,否则详细指定具体异常来处理。在发生不可恢复的异常时回滚部分操作,调用前记录状态,然后throw出来告诉调用人。

  12. 如果要重新抛出不同的异常,来维系方法的协定,应抛出具体的异常,不能是其他异常类型的基类。可以在异常对象的Data属性添加更多异常信息。

  13. 反射调用方法时,CLR内部捕捉任何异常会把他转换成TargetInvocationException,然后只能通过InnerException属性来查看真正异常。dynamic能直接抛出异常,是代替反射的一个理由。

  14. 异常发生时,CLR向上匹配catch,如果未匹配到则就是未处理的异常,任何线程有未处理异常,都会终止进程。windows会想事件日志写入记录,还可通过系统操作中心获取更多细节。

  15. winform未捕捉异常处理:Froms.NativeWindow.OnThreadException、Forms.Application.OnThreadException、Forms.Application.ThreadException.

  16. WCF未捕捉异常处理:System.ServiceModel.Dispatcher.ChannelDispatcher.ErrorHandlers属性。

  17. PrepareConstrainedRegions方法:除非保证关联的catch和finally块中的代码得到执行,否则try中代码不执行。

  18. 代码协定(System.Diagnostics.Contracts.Contract):提供了直接在代码中声明代码设计决策的一种方式,有前条件、后条件、对象不变性。代码协定有利于代码的使用、理解、进化、测试、文档和早期错误检测。

21.托管堆和垃圾回收
  1. 访问资源步骤:调用IL指令newobj分配内存-->初始化内存(实例构造器)-->访问类型成员来使用资源-->摧毁资源状态以进行清理-->释放内存,垃圾回收期独自负责这一步。

  2. 现在的内存泄漏一般是因为在集合中存储了对象,但不需要对象的时候一直不去删除。

  3. 特殊对象可调用Dispose来清理资源,其他的则有GC负责自动回收。

  4. CLR要求所有对象都从托管堆分配,地址空间满后CLR会分配更多区域(受进程的虚拟地址空间的限制32位最多1.5GB,64位最多8TB)。

  5. new操作步骤:计算类型及基类字段所需字节数-->加上对象的开销(类型对象指针和同步块索引)-->CLR如果空间足够则分配空间返回引用,NextObjPtr会进行累加到下个地址值。

  6. 当堆满时即发生垃圾回收,COM组件采用引用计数算法,因为循环引用问题CLR改为使用一种引用跟踪算法,只关心能引用堆上对象的引用类型。将所有引用类型的变量都称为根。

  7. GC标记阶段:暂定所有线程-->全部对象同步索引块标为0-->查看活动根引用对象,如果引用则更新索引块为1,知道所有根检查完毕

  8. GC压缩阶段:使幸存对象在内存中“局部化”,减小工作集提升性能-->重置引用对象指针地址-->NwxtObjPtr指向幸存对象后的位置,以便分配给下个对象。

  9. 如果GC后仍无空间,则再创建对象则将抛出OutOfMemoryException。静态字段占用空间一直到AppDomain卸载为止。

  10. GC是基于代的垃圾回收器:对象越新,生存期越短;对象越老,生存期越长;回收堆的一部分,速度快于回收整个堆。

  11. 托管堆只支持3代:第0代是新创建的对象未经历过垃圾回收,但当GC时优先考虑回收0代,第一代是经历过1次垃圾回收当GC预算回收0代仍不够时才会回收1代,经历过回收的1代则升级为2代。

  12. 垃圾回收触发条件:1.显式调用GC.Collect方法;2.windows报告系统内存低;3.CLR正在卸载appdomain;4.CLR正在关闭;

  13. 目前85000字节以上的为大对象,不同对待:1.在非小对象空间分配;2.GC不压缩大对象;3.大对象总是第2代;

  14. GC回收模式:1.工作站(默认),假定机器运行的其他程序不会消耗太多CPU;2.服务器,要求多CPU,若单处理器总为工作站模式,并假定机器只为GC服务。

  15. 强制回收:1.大量对象死亡;2.程序初始化后或用户保存一个数据文件后;

  16. 可以使用CollectionCount和GetTotalMemory方法来查看某一代发生了多少次GC以及托管堆中的对象当前使用了多少内存,还可以使用性能监视器来监视CLR内存使用情况。

  17. 特殊清理的类型:如文件流等操作本机资源的对象GC不能直接回收内存,会通过终结机制先调用对象的Finalize方法(析构器),然后再回收内存。特殊语法:方法前加“~类名()”;

  18. using语句初始化对象,只能应用于实现了IDisposable接口的类型。dispose会关闭数据流。StreamWriter必须显式指定Dispose。

  19. 为本机资源提供可以通过AddMemoryPressure和RemoveMemoryPressure方法来实现告诉GC需要多少内存。

  20. 当GC时先将有finalize方法的放入一个队列,并且将对象放入终结列表,执行finalize方法,最后再次执行GC时清理原有对象。

  21. 为了监视或控制对象的生存期,可调用GCHandle的静态方法Alloc,并传递引用以及指定如何控制(监视存在、控制生存期)。Fixed语句可以在代码块中固定一个对象。

22.CLR寄宿和AppDomain
  1. 寄宿使任何应用程序都能利用CLR的功能,AppDomain允许第三方不受信任的代码在先用的进程中运行,而CLR保证数据结构和安全上下文不被滥用。寄宿、AppDomain与程序集加载和反射一起用。

  2. 托管程序启动-->碟片检查程序生成的CLR版本信息-->设置宿主管理器(分配内存、加载资源)-->获取CLR管理器(告诉CLR阻止使用某些类、成员,调试信息,事件关注等)-->初始化启动CLR(4.0后一个进程可同时加载多个版本)-->加载执行代码-->停止CLR。

  3. AppDomain是一组程序集的逻辑容器,CLR初始化时会创建默认appdomain:1.不同appdomain间不能互相访问对象;2.可以卸载(appdomain卸载所有包含的程序集);3.可以设置权限集;4.可单独配置。

  4. 一个进程中可包含多个appdomain和一个“appdomain中立”加载的程序集来共享资源,以及包含一个执行引擎(碟片-->CLR.dll)。其中本机代码和对象都不互相共享。

  5. 共享AppDomain对象:1.按引用封送;2.按值封送;3.完全不能封送类型;

  6. 可以通过AppDomain.Unload方法卸载,以及通过设置MonitoringEnabled为true来监视AppDomain。可以为事件FirstChanceException添加委托来实现异常事件登记。

  7. 可执行程序为自寄宿(自己容纳CLR)的应用程序,创建进程时加载碟片,检查CLR信息,加载对应版本CLR到晋城,加载后判断入口方(main),CLR调用该方法启动程序。

  8. ASP.NET作为一个ISAPI DLL实现AppDomain,客户首次请求DLL处理的URL时,会加载CLR,然后CLR创建新的AppDomain。

  9. 高级宿主控制:1.使用托管代码管理CLR;2.写健壮的宿主应用程序;3.宿主拿回它的线程。

23.程序集加载和反射
  1. CLR使用System.Reflection.Assembly静态Load方法尝试加载程序集。也可调用LoadFrom通过路径或URL加载DLL。

  2. 将DLL合并到exe:应用属性设置为“嵌入的资源”-->向AppDomain的ResolveAssembly事件等级回调方法(查看资源文件dll,并Load程序集)。

  3. 早期绑定:在编译时就确定要使用的类型和方法。晚期绑定:当应用程序需要从特定程序集加载特定类型执行。

  4. 反射缺点:1.造成编译时无法保证类型安全性;2.反射速度慢(要通过字符串搜索类型);优化方案:1.让类型从编译时已知类型派生;2.让类型实现编译时已知的接口;

  5. 可通过Assembly的ExportedTypes属性判断程序集定义了哪些类型。

  6. GetTyp指定类型命名空间.类型 全称字符串,不支持基元类型。typeof来比较类型最快。

  7. 获取Type派生类型创建实例:1.System.Activator.CreateInstance方法;2.System.Activator.CreateInstanceFrom;3.System.AppDomain的方法;4.System.Reflection.Constructorinfo.Invoke;

  8. 托管可扩展性框架:Managed Extensibility Framework,MEF。

  9. 使用MemberInfo发现类型成员(方法、字段、属性等);调用:1.FieldInfo(GetValue、SetValue);2.ConstructorInfo(Invoke调用构造器);3.MethodInfo(Invoke);4.PropertyInfo(CanRead/Write、Get/SetValue);5.EventInfo(AddEventHandler、RemoveEventHandler);

  10. 使用绑定句柄减少进程的内存消耗,当需要时再加载对象,句柄类型:RuntimeTypeHandle、RuntimeFieldHandle、RuntimeMethodHandle。

24.运行时序列化
  1. 序列化是将对象或对象图转换成字节流的过程,反序列化是将字节流转换回对象图的过程。两个格式化器:BinaryFormatter、SoapFormatter。可将多个对象图序列化到一个流中。

  2. 使用特性System.SerializableAttribute(NonSerializedAttribute标示不序列化的成员)来标记对象可序列化(只能应用于引用类型、值类型、(枚举类型、委托类型 自带序列化特性))。序列化类不能用自动实现的属性。标记OnDeserialized特性表示执行反序列化时执行的方法,还有其他事件可对序列化过程控制。用OptionalField标记可选序列化字段。

  3. 序列化过程是基于反射实现的,反序列化时会判断程序集是否加载,没加载的加载,没有找到程序集的话抛异常。

  4. 不用反射序列化的话,可实现接口System.Runtime.Serialization.ISerializable接口,控制序列化和反序列化的数据。

  5. 通过StreamingContext来实现流上下文,来源目的地的控制,可实现深度复制引用对象。可以序列化为不同类型以及反序列化为不同对象。

  6. 可通过序列化代理,ISerializationSurrogate接口实现代理类型,接管序列化流程,实现类型实现的一部分的代码重写类型。可以实现继承SerializationBinder类来重写程序集或类型或版本信息等。

26.线程基础
  1. 线程的职责就是对CPU进行虚拟化。Windows大约30毫秒执行一次CPU切换。减少切换,减少线程数来优化系统。

  2. 线程缺点:创建、销毁、管理、切换;优点:程序更健壮、响应更灵敏,性能(可用于提前编译、检查拼写等);创建进程要几秒钟,比创建线程昂贵:分配内存、初始化、加载dll。

  3. 一般CLR线程就是windows线程。使用线程的环境:1.需要改变优先级;2.需要表现为前台线程;3.需要长时间运行;4.要启动及随时终止线程;

  4. 每个线程都分配了从0到31(最高)的优先级,并从高级开始轮流调用线程(如果有待调度的线程将永远不会调用低级线程,饥饿)。

  5. 系统只有1个0级线程,是在空闲时清理RAM,高级线程准备好时会抢占低级线程。进程有优先级类,然后再根据线程优先级调度。

27.计算限制的异步操作
  1. 一个进程可以有多个CLR,每个CLR可以有多个appdomain,每个CLR一个线程池,所有appdomain共享。

  2. 线程池维护一个操作请求队列,当有新请求是会创建一个线程,使用完后并不销毁。当所有线程闲置一段时间后,线程会终止自己来释放资源。

  3. 使用ThreadPool.QueueUserWorkItem来向线程池添加一个工作项。

  4. 线程执行上下文:安全设置(压缩栈、Principal属性、windows身份)、宿主设置、逻辑调用上下文数据。原线程上下文会复制给新线程来传递信息,可以使用ExecutionContext来控制传递。

  5. 使用CancellationTokenSource的Tokne(CancellationToken)属性,传递给线程,然后在线程外调用cts.Cancel(),线程判断token.IsCanCellationRequested属性是否为true,来达到取消线程目的。

  6. 使用Task<TResult>对象创建任务,可调用对象Result属性获取方法返回值。任务中异常会被吞噬,调用wait(WaitAny、WaitAll)或result(内部会自动调用wait方法)属性时,会抛出AggregateException对象。

  7. 创建任务是传递cts对象的token,然后在线程外调用cts.cancel(),然后线程中放置token.ThrowIfCancellationRequested()抛出取消异常,在线程外wait或t.result处捕捉AggregateException异常,来达到任务取消目的,使用抛异常而非属性判断是否取消任务是为了区分任务是否正常执行完毕。

  8. 可以使用Task对象的ContinueWith方法设置当任务完成时执行新的任务,并可设置什么条件下才会执行。

  9. 创建任务时指定AttachedToParent标志来表示当前为子任务,只有所有子任务执行完毕后,父任务才任务执行结束。ContinueWith即为子任务。

  10. 如果通过Task的Status属性得到任务结果是Faulted,则可以调用任务的Exception属性来查看多有异常。

  11. TaskFactory可以创建一组相同配置的子任务,并能带有返回值,以及相同的token,可以使用ContinueWhenAll方法来等待所有线程完成后执行,以及设置ContinueWith执行的条件,如取消。

  12. TaskScheduler是任务调度器,默认均为线程池任务调度器,TaskScheduler.FromCurrentSynchronizationContext()方法可获取主线程调度器,从而操作UI界面控件。

  13. 使用Tasks.Parallel的for、foreach、invoke(调用方法组),来实现并行操作。还可指定每个任务执行前后要调用的方法。

  14. 在linq中通过调用AsParallel扩展方法来实现启用linq方法的并行版方法。使用AsSequential切换会顺序查询。可以调用WithExecutionMode来让系统决定是否启用并行。

  15. Threading的Timer:一个线程控制的最好的定时器;Win.Forms的Timer:触发时将计时器消息注入线程的消息队列;Timers的timer:完全不要用;

  16. 可以利用Task的静态Delay方法和c#的async和await关键字来实现定时操作。先返回await前的内容执行主线程,等待时间到后继续执行await后的内容。

  17. 最好不要设置线程池线程数量默认1000。

28.I/O限制的异步操作
  1. IO读写:.net-->win32-->win内核-->硬盘IRP(请求包)队列-->线程阻塞-->原路返回;IO异步:-->IRP队列-->线程返回继续运行 用异步方法返回的task的continuewith来读取异步返回的数据。

  2. 使用async Task<TResult> 来声明方法,并在方法中使用await关键字来实现方法的异步操作。使用whenAll或whenAny来控制后续方法执行。

  3. 异步函数:以Async为后缀的方法,返回结果为Task。以bengin、endxx方法和IAsyncResult接口以及XxxAsync方法不返回task的异步已经过时。可以使用FromAsync封装旧异步模型为新模型。

  4. 项目中实现异步:asp.net在.aspx文件中添加Async="true";MVC则让方法返回一个Task<ActionResult>;WCF让服务返回Task或Task<TResult>;

  5. asp.net网站维护一个线程池,当有新访问时会启用一个线程,因为线程有限所以可以将耗时任务使用异步操作来达到最大化利用线程池的目的。

  6. 使用FileStream时值FileOptions.Asynchronous标志来指明自己选择,以及是否使用ReadAsync方法。可以使用P/Invoke来执行I/O请求优先级。异步是放在硬件请求队列中所以不存在线程同步问题

29.基元线程同步构造
  1. 线程安全的方法意味着两个线程试图同时访问数据时,数据不会被破坏。FCL保证所有静态方法都是线程安全的。

  2. 基元用户模式线程同步构造(首选):易变构造(volatile关键词告诉编译器将字段放入RAM而非CPU寄存器)、互锁构造(Interlocked原子操作,可以用此构建自旋锁)。

  3. Interlocked.CompareExchange提供重载来操作其他类型。

  4. 可以通过创建一个具有指定名称的内核对象,实现进程启动时检查是否有已经运行,或用此来共享内核对象来实现进程间通讯。

  5. 内核模式比用户模式慢,但竞争失败的线程不会“自旋”,减少CPU时间浪费,WaitHandle为基类。

  6. Event内部维护Boolean变量,基类:EventWaitHandle,自动:AutoResetEvent(WaitOne-->set 每次过一个线程),手动:ManualResetEvent(WaitOne-->set-->Reset 每次可过多个线程)。

  7. 信号量(Semaphore):控制可运行线程的数量,内部维护int类型,每次运行一个线程则减少1,运行完则增加1来通知可继续运行其他线程。

  8. 互斥体(mutex):必须同一个线程操作请求和解锁,同时一个线程可多次请求,但也必须多次释放,当计数为0时则其他线程才可使用mutext(多线程可通过字符串指定同一对象)。

30.混合线程同步构造
  1. 可以混合使用Interllocked(用户模式,快)和AutoResetEvent(内核模式,慢,但能减少cpu时间)来制造高性能混合锁。

  2. Slim后缀的类表示位混合构造锁,实现没有竞争情况下内部自旋,为用户模式,当有竞争时转换为内核模式。

  3. Monitor:支持自旋、线程所有权和递归的互斥锁,Enter、Exit;lock相当于try、finlly中调用monitor;不要传递string类型以及要定义静态私有引用对象来传递给monitor。

  4. ReaderWriterLockSlim类实现可并发读取数据,当有写入时便阻塞线程的功能(作者自己的更快的类OneManyLock)。

  5. CountdownEvent类和Semaphore行为相反,阻塞一个线程,直到他的内部计数器变为0.Barrier可控制一系列线程在一个算法的不同阶段推进。

  6. 优先选择volatile、interlocked、spinlock、monitor、task的后续任务、异步操作,最好不要用try块。

  7. 双检索技术:多线程时利用延迟初始化,当首个线程访问时阻塞所有线程同时初始化线程,其他线程访问时直接返回对象引用。

  8. 使用monitor类中pulse、pulseall、wait来实现条件变量模式,可避免线程连续自旋不断检测条件浪费cpu 的问题。

  9. 异步的同步构造:使用SemaphoreSlim的WaitAsync方法实现异步等待锁可用的目的,从而使线程不阻塞。

  10. FCL自带线程安全集合类:ConcurrentQueue(先入先出),ConcurrentStack(后入先出),ConcurrentDictionary(无序Key/Value对集合),ConcurrentBag(无序集合)。

posted @ 2015-03-19 18:13  炳森之火  阅读(352)  评论(0编辑  收藏  举报