CLRvia3读书笔记

// 第1章 CLR的执行模型
1.CLR Common Language Runtime 公共语言运行时
2.托管模块 managed module 托管模块各部分(PE32或PE32+头、CLR头、元数据(metadata)、IL(中间语言)代码) [PE=portable Executable]
3.类型库(Type Library) 接口定义语言(Interface Define Language)
4.%SystemRoot%System32  目录中 MSCoreEE.dll 判断.net framework是否安装
5..NET Framework SDK CLRVer.exe -all 可以列出所有版本
6./platform 开关 (anycpu[默认],x86,x64,Itanium)  WoW64(Windows on Windows64)技术允许32位的windows应用程序,在64位上运行
7.ILAsm.exe 汇编器,ILDasm.exe 反汇编器
8.JIT Compiler Just-in-time 即时编译器
9./optimeze /debug 开关 涉及 IL优化和JIT本地代码质量优化(pdb文件[Program Database])
10.PEVerify.exe查看不安全代码
11.本地代码生成器 NGen.exe
12.通用类型系统CTS(Common Type System) [包括字段Field,方法Method,属性Property,事件Event]
13.CLI Common Language Infrastructure 公共语言基础结构
14.CLS Common Language Specification 公共语言规范
15.(与非托管代码的交互)Type Library Importer 工具和P/Invoke Interop Assistant 工具源代码 http://CLRInterop.CodePlex.com
// 第2章 生成、打包、部署和管理应用程序及类型
1.csc.exe 命令行工具使用 cd /d C:\Windows\Microsoft.NET\Framework\v4.0.30319  csc /? 查看帮助
2.C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.rsp  rsp响应文件  格式(csc.exe @xxx.rsp xx1.cs xx2.cs)
3.AL.exe 命令行,用visual studio 命令行进入使用
4.配置文件 runtime 节点的使用,可以先从配置文件加载dll, 测试目录可以以 ; 分割




// 第3章 共享程序集和强命名程序集
1.FCL Framework Class Library
2.SN.exe 命令行用法
3.GAC Global Assembly Cache 全局程序缓存集
4.GACUtil.exe 命令行用法
5.延迟签名
6.参看配置文件




// 第4章 类型基础
1.所有类型都是从 System.Object派生,Object public protect static 方法
2.is  as 关键字
3.using AliasName=System.Text.StringBuilder;
4.外部别名 (extern alias), 1.csc /r 命令行 2.选中程序集,查看属性,修改别名
5.线程栈 与 托管堆(类型对象指针,同步索引块,静态字段 and 其他)




// 第5章 基元类型、引用类型和值类型
1.基元类型(primitive type),基元类型可以直接映射到Framework类库(FCL)中存在的类型
C#基元类型 FLC类型 CLS是否相容 说明
sbyte System.Sbyte 8位
byte System.Byte 8位
short System.Int16 16位
ushort System.UInt16 16位
int System.Int32 32位
uint System.UInt32 32位
long System.Int64 64位
ulong System.UInt64 64位
char System.Char 16位Unicode字符(C++中是8位)
float System.Single 32位
double System.Double 64位
bool System.Boolean 1个true false 值
decimal System.Decimal 128位
string System.String  
object System.Object  
dynamic System.Object  跟object一样


2.checked unchecked 基元类型操作,也可以对代码块操作,也可以在项目属性中设置(生成,高级,检查上溢出,下溢出操作)
异常:System.OverflowException
System.Numerics.BigInteger(可能抛出 OutOfMemoryException) / Decimal CLR认为不是基元类型,所以checked unchecked对其不起作用
3.引用类型 值类型
值类型:结构(struct)或枚举(enum), System.Eum 抽象类型<-- System.ValueType <-- System.Object
引用类型:class
4.Equal GetHashCode 重写时一起重写,否则会给出警告
5.值类型的两种状态:unboxed 未装箱,boxed装箱 [装箱后为引用类型,引用类型垃圾回收需要调用 Finalize,值类型不需要]
6.CLR如果控制类型中字段的布局[System.Runtime.InteropServices.StructLayout(LayoutKind.Auto)] 
7.对象的相等性(Equal,有些需要重载)与同一性(GetHashCode,Object 静态方法 public static bool ReferenceEquals(object objA, object objB)) 
8.对象哈希码(GetHashCode可以重写,System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(new object()) 获取唯一的hashcode)
9.dynamic(可以用局部变量,字段,参数) var(只能用在声明方法内部局部变量) 




// 第6章 类型和成员基础
1.类型中的成员:常量,字段,实例构造器,类型构造器,方法,操作重载符,属性,事件,类型
2.友元程序集 using System.Runtime.CompilerServices;
  [assembly:InternalsVisibleTo("Wintelllect,PublicKey=222...222")]
3. CLR术语 C#术语
Private private
Family protected
Family and Assembly 不支持
Assembly internal
Family or Assembly protected internal
Public public
4.partial
5.面向对象编程(oop:Object-Oriented Programming) 组件软件编程(csp:Component Software Programming)
代码访问安全性(Code Access Security,CAS) 
6.major version:minor version:build number:revision 主版本号:次版本号:内部版本:修订号
7.abstract virtual override sealed new(用在方法上 表示跟父类没有关系)
8.call IL指令调用静态方法,实例方法和虚方法(快,无需检查类型是否为空,不抛出异常)
  callvir 调用实例方法和虚方法,不能调用静态方法(慢,会抛出 NullReferenceException)
9.new virtual override




// 第7章 常量和字段
1.非基元类型 (const)常量初始值须设置为 null,C#不允许常量指定static关键字,因为常量总是隐式为static
static readonly 可以搭配使用
2.字段
CLR术语 C#术语
Static static
Instance 默认
InitOnly readonly
Voliatile volatile(编译器.clr,硬件不会执行一些"线程不安全"的优化措施)
3. readonly 引用类型,不可以变得是引用,字段是可以改变的




// 第8章 方法
1.class 默认无参构造函数,一旦有构造函数,clr就不生成 无参构造函数了. 见 SomeType1
2.私有变量 赋值过的,会在"每个构造函数"中初始化,如 SomeType2, 更好的方法是显示调用构造器,如:SomeType22(一旦显示调用任意构造函数,那么就不会初始化变量了)
3.值类型, C#不允许定义无参构造函数, CLR允许,但是CLR默认不调用构造函数,必须手动调用
4.类型构造器(静态构造器) 引用类型见 SomeType1   ,值类型见SomeValueType 中Point(不会被调用)
5.类型构造器的性能:初始化的时机  见 BeforeFieldInit,(public auto ansi beforefieldinit) beforefieldinit 标示,告诉CLR提早初始化好静态字段
6.操作符重载操作符重载,实际是生成固定特殊 形如:op_....,而且限制诸多
7.转换操作方,主要是看 函数签名,是否有"装箱,拆箱"  static implicit operator(){} (隐式声明)  static explicit operator(){}(显式声明) 关键字
8.扩展方法 (ExtensionAttribute) 该类 在 System.Core.dll,当一个类 被 this 标识的时候会加这个Attribute
扩展接口,扩展委托的一个demo 见:ShowItemDemo
9.分部方法 partial 标记类(把类分开), 标记方法, 可以只有声明,如果被调用,则IL不会生成相应代码,有实现的时候就会生成
10.
// 第9章 参数
1.Class1 演示了可选参数和命名参数,参数的计算顺序都是从左到右,ref out 不能设置默认参数
默认参数的Attribute:System.Runtime.InteropServices.OptionalAttribute
默认值Attribute:System.Runtime.InteropServices.DefaultParameterValueAttribute
2.ref out 也算一种重载,但是二者只能用一个; 且两个关键字参数类型必须一致,见 Class1.Test
3.利用泛型解决 ref,out 类型一直问题,见SomeMethod,下面是系统的例子
//System.Threading.Interlocked.Exchange()
     //System.Threading.Interlocked.CompareExchange()
4.params 标记 System.ParamArrayAttribute ,会造成一定的性能损失,所以参见 System.String 的 Concat方法怎么实现
5.参数返回类型 IEnumerate (接口,弱类型),IList(强类型),List
Stream, 不用 FileStream,NetworkStream,MemorySteam
参数有用不同的规则,所以要看情况
// 第10章 属性
1.属性(property),无参属性(parameterless property),有参属性(parameterful property),索引器(indexer),匿名类型和System.Tuple
2.数据封装(data encapsulation),访问器(accessor)
3.get set IL默认生成 get_xx set_xx 方法
4.自动实现属性 Automatically Implemented Property(AIP)  Name {get;set;}
5.属性不能 out ref 传参
6.属性方法可能花费更长的时间来执行(执行线程同步,可能造成线程永远终止),所以 可以远程访问的类(System.MashalByRefObject 派生类)不应该用属性,而应该使用方法
7.LINQ Language Intergrated Query,语言集成查询
8.System.Tuple 类型, dynamic e=new System.Dynamic.ExpandoObject(); 实际是一个 IDictionary 对象集合
9.有参属性(parameterful property),System.Drawing.Imaging.ColorMatrix 类提供了多个参数的索引器
10.IL 生成索引器的操作集合是 Item 也即 get_Item set_Item,用 IndexrNameAttribute 更改, 多个索引器的名称必须一致
11.有参,无参属性都IL成方法,所以 System.Reflection.PropertyInfo 类可以发现
12.属性访问器采用内联(inline),把代码直接编译到他的方法,所以发布后快,调式慢
13.泛型属性访问器,C#不支持;索引器不支持在类型上
// 第11章 事件
1.EventHandler 原型委托,System.EventArgs 参数原型
事件没有返回值,因为事件有好多个,但是FCL中ResolveEventHandler返回了Assembly类型的一个对象
2.线程安全用到的类 Thread.VolatileRead和Interlocked.CompareExchange
3.System.Reflection.EventInfo 获取event的信息
4.EventSet类跟系统中 System.ComponentModel.EventHandlerList对比
System.ComponentModel.EventHandlerList 1.使用的是链表2.没有提供安全线程
5.编写一个事件,参见EventDemo文件
// 1.设计公开事件
    // 1.1定义类型来容纳所有需要发送给事件通知接受者的附加信息
// 1.2定义事件成员
// 1.3定义负责引发事件的方法通知事件的登记对象
// 1.4定义方法将输入转化为期望事件


// 2.编译器如何实现 见 CompilerEvent
add_xx remove_xx


// 3.设计侦听事件类型 参见 Fax 类

// 4.显示实现事件 参见 EventSet
System.Threading.Monitor


// 第12章 泛型
1.泛型(generic)是CLR和编程语言提供的一种特殊机制,它支持另一种形式的代码重用,即"算法重用".
源代码保护,类型安全,更加清晰的代码,更佳的性能(不定义为Object) 参考类[OperationTimer]
2.CLR允许泛型创建泛型引用类型和泛型值类型,不允许创建泛型枚举类型.除此外还可以撞见泛型接口和泛型委托.
CLR也允许在引用类型、值类型或接口中定义泛型方法
3.FCL的泛型: 
集合:System.Collections.Generic List ,  System.Collections.ObjectModel
线程安全:System.Collections.Concurrent
T成为 类型参数(Type Parameter)
4.System.Array 提供了大量静态泛型方法 Sort,BinarySearch
5.Wintellect的Power Collections库, 使CLR可以使用C++的标准模板库(Standard Template Library,STL)的部分集合类
BigList,Bag,OrderedBag,Set,OrderedSet,Deque,OrderedDictionary,MultiDictionary,OrderedMultiDictionary
http://www.wintellect.com/
6.智能感知(IntelliSense)
7.开放类型(open type)和封闭类型(Closed type)
参见 OpenAndClosedType
类型名称"`[数字]",数字代表类型的元数,也即所需参数的个数(Activator.CreateInstance)
泛型的静态对象,构造函数..泛型类型对象都会执行一遍
8.泛型类的同一性,参见 GenericTypeIsSame
9.代码爆炸(code explosion), 值类型每个类型生成一份本地代码,引用类型会生成一份代码
10.泛型接口  参考Triangle
没有泛型接口,每次试图使用非泛型接口(如IComparable)操纵一个值类型都会发生装箱操作
11.委托
public delegate TReturn CallMe(TKey key, TValue value);
编译器解释成:一个构造器,一个Invoke,一个BeginInvoke,一个EndInvoke
尽量使用系统中Action和Func
12.委托和接口的逆变和协变泛型类型实参
1.不变量(invariant),参数类型不可更改
2.逆变量(contravariant),可以从基类更改为派生类,用用in / ref(也可以)标记,只能作为输入位置; contravariance(逆变性)
3.协变量(covariant),可以从一个派生类更改为他的基类,用out标记,返回值类型;  covariance(协变性)
同一个T 不能同时使用 ref或out,便不允许可变.
不能通过编译:delegate void D(T t); delegate void D(in T t);
可以编译:delegate void D(ref T t); delegate void D(out T t);delegate void D(T t);
13.泛型和其他成员
属性,索引器,事件,操作符方法,构造器,终结器(finalizer) 本身不能有类型参数
14.可验证性约束 参见 Triangle文件
where T:Comparable,重写泛型方法时,不能改变约束
1.主要约束
可以有0个或1个主要约束,可以是一个引用类型,也可以是 struct,class (两个特殊的主要约束) [Sytem.Nullable]
但不可以是 System.Object,System.Array,System.Delegate,System.MulticastDelegate,System.ValueType,System.Enum,System.Void
2.次要约束
可以有0个或多个次要约束
a)可以是口类型
b)另外一种参数类型约束(type parameter constraint),也叫 裸类型约束(naked type constraint)
public List ConvertIList(IList list):where T:TBase{} 
3.构造器约束
0个或1个  where:new()  [new(),不能和struct 一起用,多余]
4.其他可验证问题
a)泛型类型变量的转型 int a=(int)(Object)T;
b)将一个泛型类型变量设为默认值 default(T)
c)将一个泛型类型变量与null比较 T==null , 如果T约束为 Struct 则报错
d)两个泛型比较 C(T t1,T t1){if(t1==t2)} 会报错,因为不确定 T 是否有 == !=重载符
e)泛型类型变量作为操作数使用 T++ ,报错,因为T可能没有 ++ += < 等运算重载
// 第13章 接口
1.类和接口继承
接口"实现"了多继承(Multiple inheritance), 所有类都继承自Object
2.定义接口
接口对一组方法签名进行统一命名,接口还可以定义事件,无参属性,有参属性(索引器)
虽然CLR允许接口有静态方法,静态字段,常量和静态构造器,但CLS相容的接口不允许,C#也禁止
3.继承接口
方法 作用关键字 默认无(virtual和sealed),virtual,sealed(必须是override才可以用)
4.关于调用接口方法的更多探讨
接口引用类型,值类型的接口要装箱操作
5.隐式和显示接口方法实现(幕后发生的事情) [显示接口实现方法(Explicit Interface Method Implementation EIMI)]
接口和实现都有一套自己的引用方法地址;调用那个关键要看 地址的指向
显示接口private的,只有接口才可以访问,隐式相应的类访问


virtual 跟 override 访问性要看实际的类型,也即重写了父类的方法,只能在内部用base 调用
6.泛型接口
避免装箱,保护类型安全性
7.泛型和接口约束
接口是引用类型,值类型需要装箱
8.实现多个具有相同方法名和签名的接口
使用的时候必须转换成接口使用
9.用显示接口方法实现来增强编译时类型安全
显示接口增强系统的安全性,避免非预期的装箱
10.谨慎使用显示接口方法实现
1.模糊地带EIMI
2.值类型装箱问题
3.EIMI不能由派生类调用
11.设计:基类还是接口
1.is-a vs can-do
2.易于使用
3.一致性的实现
4.版本控制

// 第14章 字符、字符串和文本处理
1.字符
1.System.Char 是 struct类型,表示 16位Unicode代码值
public struct Char : IComparable, IConvertible, IComparable, IEquatable
2.MaxValue = (char)0xffff , MinValue = '\0'
3.System.Globalization.UnicodeCategory u = Char.GetUnicodeCategory(c);
4.ToLower(),ToUpper()依赖 System.Threading.Thread.CurrentCulture
5.ToLowerInvariant,ToUpperInvariant 忽略语言文化(culture)的方式转换
6. c = '\u00bc';
double d = Char.GetNumericValue(c);
7.值类型与char转换,效率按优先顺序列出
1.转型(强制类型转换),直接生产IL
2.使用Convert 以checked方式转换,会抛出 OverflowException
3.使用IConvertible接口 要装箱,效率差,会抛出 System.InvalidException
2.System.String类型
1.String 不可变(immutable),C#将String视为基元类型
public sealed class String : IComparable, ICloneable, IConvertible, IComparable, IEnumerable, IEnumerable, IEquatable
2.IL中 ldstr = load string 加载字符串
3.System.Environment 构造当前环境的特殊字符
4.@ 逐字字符串(verbatim strings)
5.字符串的语言文化 SystemComparison
System.Globalization.CultureInfo; 线程关联属性:CurrentUICulture,CurrentCulture
6.字符串对比, System.Globalization.CompareInfo
字符串展开(character expansion),CompareOptions
7.字符串留用,与NET版本有关,有事后指定标记也无效
// 摘要: 
//   控制由公共语言运行时的实时 (JIT) 编译器生成的代码的严格性。
[assembly: System.Runtime.CompilerServices.CompilationRelaxations(CompilationRelaxations.NoStringInterning)]
   string s1 = "hello";
            string s2 = "hello";
            // 测试结果 true
            bool b1 = object.ReferenceEquals(s1, s2);


            s1 = String.Intern(s1);
            s2 = String.Intern(s2);
            // 测试结果 true
            bool b2 = object.ReferenceEquals(s1, s2);
8.字符串池
9.检查字符串的字符和文本元素
Length.Chars,GetEnumerator,ToCharArray,Contains,IndexOf,LastIndexOf,IndexOfAny,LastIndexOfAny
当Unicode字符串用多个16位值来表示的时候出现 高位代理项(high surrogate),低位代理(low surrogate)
System.Globalization.StringInfo中有用的两个属性(LengthInTextElements,SubstringByTextElements)
StringInfo.GetTextElementEnumerator,StringInfo.PareCombiningCharacters 参见StringInfoTest
10.其他字符串操作
Clone,Copy,CopyTo,SubString,ToString,其他静态方法
3.高效率构造字符串
System.Text.StringBuilder
4.获取对象的字符串表示:ToString
d短日期,D长日期,g常规,M表示月/日,s表示可排序(sortable),T表示长时间,u表示符合iso8601格式的协调时间
U采用长日期格式协调时间,Y表示年/月,所有枚举都支持G常规,F表示标志(falg),D表示十进制,X表示十六进制
C表示货币格式,D十进制,E科学计数,F定点格式(fix-point)格式,G常规,P百分比格式,R往返行程(round-trip)


提供定制化格式器:参见BoldInt32s
5.解析字符串来获取对象:Parse
Parse 和 TryParse 静态方法
6.编码:字符串和字节的相互转换
高位优先或低位优先,System.Text.Encoding,System.Text,Decoder,System.Convert.FromBase64String(ToBase64CharArray,ToBase64String)
Encoding 派生类中GetByteCount 和 GetCharCount方法速度一般,因为要解析字符返回一个准确的结果,GetMaxByteCount,GetMaxCharCount 返回最坏情况的值
参见 TextTest
7.安全字符串
System.Security.SecureString 实现了 IDispose接口来摧毁内容
System.Runtime.InteropServices.Marshal.
参考:SecureStringTest


// 第15章 枚举类型和位标识
1.枚举类型(enumerated types)
1.枚举派生自 System.Enum,Emum派生自System.ValueType,ValueType派生自System.Object
2.枚举类型定义的符号是常量值(EnumTest:Color1),编译的时候会用值去替换,运行的时候不需要,所以枚举的符号只有编译的时候才需要
参见:EnumTest
2.位标志
1.[Flags] 参见:FileAttributes
2.public bool HasFlag(Enum flag); 会装箱
3.Parse TryParse, Flag 可以用 | 按位与操作,可以 ToString(),打印出满足条件的集合(,分割)
3.为枚举类型添加方法
枚举类型不能定义方法,用扩展方法可以实现
// 第16章 数组
0.  a)CLR支持一维数组、多维数组、交错数组(jagged array)(即由数组构成的数组)
b)所有数组类型都隐式从System.Array抽象类派生,Array派生自System.Object,意味着数组始终是引用类型,是在托管堆上进行分配的
c)CLS(Common Language Specification)公共语言规范要求所有数组必须是0基数组.
d)每个数组都关联了一些额外的开销信息,包括数组的秩(rank,数组的维数),数组每一维的下限(最好是0),每一维的长度.还包含数据的类型
e)一维0基数组有时也称SZ(single-dimension,zero-based)数组或向量(vector)是最佳的,因为有特殊的IL指令处理(newarr,ldelem,ldelema,ldelen,stelem)
f)对于0基数组,再循环的时候,JIT编译器通常只在一个循坏执行之前检查一次数组边界,而不是每次循环迭代都这么做
g)数组的定义 ArrayDefine
1.初始化数组元素
String[] names = new string[] {"Aidan", "Grant"};
     String[] names1 = new[] {"Aidan", "Grant"};
     var names2 = new string[] {"Aidan", "Grant"};
     string[] names3 = {"Aidan", "Grant"};
     //错误 1 不能使用数组初始值设定项对隐式类型的局部变量进行初始化
     //错误 2 只能使用数组初始值设定项表达式为数组类型赋值。请尝试改用 new 表达式。
     //var name4 = { "Aidan", "Grant" };
2.数值转型
1.对于引用类型数组,CLR允许将数组隐式转为另一种类型(两个数组必须维数相同,源跟目标类型存在隐式转换)
2.不能讲值类型转换为其他任何类型
// 错误 1 无法将类型“int[]”转换为“object[]”
Object[] oldim = (Object[]) aInts;
3.Array.Copy(可以处理内存的重叠区域,还可以在复制的时候进行有必要的转型)
4.IComparable 接口在Array.Copy 时可以判断是否可以转型(返回int比较鸡肋)
5.数组协变性(Array Covariance) 数组转型性
6.System.Buffer.BlockCopy 部分拷贝
public static void BlockCopy(Array src, int srcOffset, Array dst, int dstOffset, int count);
3.所有数组都隐式派生自System.Array
4.所有数组都隐式实现IEnumerable,ICollection和IList
5.数组的传递和返回
返回 !=null 的数组,foreach的时候不用判断null
6.创建下限非零的数组,参见ArrayNoZero
Array.CreateInstance
7.数组的访问性能 ArrayVisit
1.CLR可以创建下限是0的数组,有时也称SZ(single-dimension,zero-based)数组或向量(vector)
JIT在循环外面只检查一遍数组越界的情况
2.下限未知的一维或多维数组
3.fix关键字要在非安全模式下运行(计算内存地址,可以访问非预期的内存地址)
8.不安全的数组访问和固定大小的数组 StackallocDemo
通常,因为数组是引用类型,所以在一个结构中定义的数组字段实际上只是指向数组的一个指针或引用;数组本身在结构内存的外部.
要定义这种结构必须满足:
// 这个数组是以内联的方式嵌入结构,需要满足条件:
    // 1.类型必须是结构(值类型),不能是引用类型
    // 2.字段或定义的结构必须 unsafe 标记
    // 3.数组字段必须 fixed 关键字标记
    // 4.数组元素的类型必须是下面之一:Boolean,Char,SByte,Byte,Int16,Int32,UInt16,UInt32,Int64,UInt64,Single,Double
// 第17章 委托
1.初识委托 delegate [DelegateTest]
a)netframework的委托确保了回调方法的类型安全(CLR的主要目标之一)
b)协变和逆变只适用于引用类型,CLR不允许值类型的协变性和逆变性
c)所有的委托都派生自 System.MulticastDelegate,System.MulticastDelegate派生自System.Delegate(这里面有静态方法)
2.用委托回调静态方法
3.用委托回调实例方法
4.委托揭秘
// 引用类型支持协变性
    internal delegate void Feedback(Int32 value);
    // 上面代码编译器解析成下面的类,实际编译器不允许这么定义,通过IL可以查看到下面的类
// IL可以看出委托回调方法调用的是 Invoke方法
    internal class FeedBack : System.MulticastDelegate
    {
        // 构造器
        public FeedBack(Object Object, IntPtr method);


        // 这个方法和源码指定的原型一样
        public virtual void Invoke(int value);


        // 以下方法实现了对回调方法的异步回调
        public virtual IAsyncResult BeginInvoke(int value,AsyncCallback callback,Object object);
        public virtual void EndInvoke(IAsyncResult reuslt);
    }


MulticastDelegate构造参数含义:
_target(System.Object) :静态类型为null,实例方法的时候为 this
_methodPtr(System.IntPtr) :内部数值,CLR标识要回调的方法
_invovstionList(System.Object):通常为null,构造一个委托连的时候,他可以引用一个委托数组
如果一个委托是委托链,那么 _target,_methodPtr 可以忽略,直接关注 _invovstionList
5.用委托回调许多方法(委托链)
// invoke 的伪代码
public int Invoke(int value){
int result;
Delegate[] delegateSet = _invocationLsit as Delegate[];
if(delegateSet != null){
//调用每个委托
foreach(FeedBack d in delegateSet) result = d(value);
}else{
//单个委托方法
result = _methodPtr.Invoke(_target, value);
}
return result;
}
a)C#对委托的支持
1.Combine,Remove
2.+=,-+
b)取得对委托立案调用的更多控制
// invoke 的伪代码可以看出: 必须按照顺序执行,有一个抛出异常则终止或者一个方法阻塞了很差一段时间
c)针对 b,自己显式调用委托 参见 Light
6.委托定义太多(委托泛型)
1.CLR提供了满足16个参数的泛型委托(参数0-8的在MSCoreLib.dll中,9-16在System.Core.dll中)
Action 返回值 void (16个)
Func 返回值 T (17个)
2.下面的需要自己定义委托
a)ref ,out关键字
b)params 关键字
7.C#委托提供的简化语法(语法糖 Syntactical sugar)
1.不需要构造委托对象,不用 new ,编译器可以理解  参见:AClass
2.不需要定义回调方法(匿名函数,lambda表达式)
a.[System.Runtime.CompilerServices.CompilerGeneratedAttribute]匿名函数特性
b.匿名函数,第一次调用的时候会缓存(该缓存不会被垃圾回收器回收),生成的匿名函数形如:<>9__CacheAnoynmousMethodDelegate1
c.可以引用静态字段或静态方法,如果委托不是静态方法,也可以引用事例的成员
d.lambda:
// 如果委托不获取任何参数,就用 ()
            Func f = () => "Jeff";
            Console.WriteLine(f());


            // 如果委托获取一个或更多参数,可显示制定类型
            Func f2 = (int n) => n.ToString();
            Func f3 = (int n1, int n2) => (n1 + n2).ToString();
            Console.WriteLine(f2(1) + "  " + f3(2, 3));


            // 如果委托获取一个或更多参数,编译器可推断类型
            Func f4 = ( n) => n.ToString();
            Func f5 = ( n1,  n2) => (n1 + n2).ToString();
            Console.WriteLine(f4(1) + "  " + f5(2, 3));


            // 如果委托获取一个参数,可省略 ()
            Func f6 = n => n.ToString();
            Console.WriteLine(f6(4));


            // 如果委托有 ref/out 参数,必须显式指定 ref/out 和类型
            Bar b = (out Int32 n) => n = 5;
3.局部变量不需要手动包装到类中即可回调方法 BClass
// 以原子操作的形式递减指定变量的值并存储结果。
Interlocked.Decrement(ref numTodo) == 0
// 用一个指示是否将初始状态设置为终止的布尔值初始化 System.Threading.AutoResetEvent 类的新实例。
AutoResetEvent done = new AutoResetEvent(false);


// 表示线程池线程要执行的回调方法。
public delegate void WaitCallback(object state);
8.委托和反射 参见 CreateDemo
Delegate.CreateDelegate(...)
public object DynamicInvoke(params object[] args);
// 第18章 定制attribute
1.使用定制Attribute
a)它们只是将一些附加信息与某个目标元素关联起来.编译器在托管模块的元数据中生成这些额外的信息
[DllImport] : 告诉CLR该方法位于指定dll的非托管代码中
[Serializable] : 告诉序列化格式化器(serialization formatter)
[AssemblyVersion] : 版本
[Flag] : 枚举位标志
b)Attribute应用于以下定义表的记录项: SomeType
TypeDef(类,结构,枚举,接口和委托)
Method(含构造器)
ParamDef,FiledDef,PropertyDef,EventDef,AssemblyDef和ModuleDef
c#允许Attribute在:程序集,模块,类型(类,结构,枚举,接口,委托),字段,方法(含构造器),方法参数,方法返回值,属性(property),事件和泛型类型参数
c)直接或间接派生自 System.Attribute [Attribute1,Attribute2] , 省略 Attribute
2.定义自己的Attribute
//     指定另一特性类的用法。无法继承此类。
AttributeUsageAttribute:AttributeTargets , AllowMultiple , Inherited
3.Attribute的构造器和字段/属性的数据类型
属性和构造器,尽可能的用简单类型:Attribute对于一个实例,它被序列化成驻留在元数据的一个字节流
在运行时反序列化,从而构造类的一个实例.
编译器对每个构造器参数都采用这样的格式写入字节流:一个1字节长度的类型ID,后跟具体的值.
在对构造器的参数进行"序列化"之后,编译器写入字段/属性名称,后跟一个1字节的类型id,再跟上其值,从而
生产指定的每个字段和属性的值.对与数组,会先保存数组元素的个数,后跟每个单独的元素.
4.检测定制Attribute
a)enumType.IsDefined(typeof(FlagAttribute),false)  (效率高,不反序列化)
b)Attribute.GetCustomAttributes (AllowMultiple = true) 返回多个,要返序列化
c)Attribute.GetCustomAttribute (AllowMultiple = false) ,要返序列化
5.两个Attribute实例的互相匹配
System.Attribute 重写了 equals 方法,会比较两个对象的类型, Match 方法默认调用了Equal方法
参见:AccountsAttribute
6.检测定制Attribute时不创建从Attribute派生的对象
GetCustomAttributes,GetCustomAttribute 要调用构造器,而且可能调用属性的set访问器
使用 System.Reflection.CustomAttributeData 类 可以查找Attribute的同时禁止直行Attribute的代码
参见:CustomAttributeDataTest
7.条件Attribute  (CondAttribute)
条件Attribute类(conditional attribute class)
System.Diagnostics.ConditionalAttribute

// 第19章 可空值类型
1.C#对可空值类型的支持
可空值类型(nullable value type)
// 摘要: 
    //     支持可为其分配 null 的值类型,如引用类型。无法继承此类。
    [ComVisible(true)]
    public static class Nullable


/ 摘要: 
    //     表示基础类型为值类型的对象,值类型与引用类型一样也可以分配 null。
    //
    // 类型参数: 
    //   T:
    //     System.Nullable 泛型类型的基础值类型。
    [Serializable]
    [TypeDependency("System.Collections.Generic.NullableComparer`1")]
    [TypeDependency("System.Collections.Generic.NullableEqualityComparer`1")]
    public struct Nullable where T : struct{
// 下面是编译器可能的实现
// 两个字段表示状态
private bool hasVaule = false; // 假定为null
internal T value = default(T); //假定所有位都为 0


public Nullable(T value){
this.vaule = value;
this.hasVaule = true;
}  
public static implicit operator T?(T value){
return new Nullable(value);

public static explicit operator T(T? value); 
public T GetValueOrDefault(){return value;} 
public T GetValueOrDefault(T defaultValue){
if(!hasValue) return defaultValue;
return value;

public override bool Equals(object other){
if(!hasValue) return {other == null};
if(other == null) return false;
return value.Equals(other);

public override int GetHashCode(){
if(!hasValue) return 0;
return value.GetHashCode();

public override string ToString(){
if(!hasValue) retrun "";
return value.ToString();
}   
public bool HasValue { get{return hasValue;} }  
public T Value {get{
if(!hasValue){
throw new InvalidOperationException("Nullable object must be have a value.");
}
return value;
}}


}


a)Nullable b)在可空类型上执行转换
c)一元 二元 操作 (Operators)
/*
          * null & true:
          * null | true True
          * null & false False
          * null | false 
          * null & null 编译器不允许
          * null | null 编译器不允许             * 
 */
一元操作符(+ ++ - -- ! ~):操作符是null,结果就是null
二元操作符 (+ - * / % & | ^ << >>):两个操作符任意一个是null就是null,上面&|给出了特殊情况
相等性 == !=:两个操作符都是null相等,一个操作数是null,两者不等,其他正常逻辑
比较(关系)操作符(< > >= <=):两个操作数任意一个是null,结果就是null
d)重定义 == != 操作符
2.C#的空接合操作符(null-coalescing operator)
??
int b = a ?? 0;
3.CLR对可空值类型的特殊支持
a) int? n = null;
  object o = n;
  n==o (true)
b)装箱,拆箱
回报 NullReferenceException
c)可空值类型调用 GetType = System.Nullable
d)通过可空值联系调用接口方法,要强制转换成接口

// 第20章 异常和状态管理
1.定义"异常"
a)exception handling 异常处理
b)结构化异常处理(Structured Exception Handling)
2.异常处理机制
a)try..catch(0个或多个)..finally(0个或一个)
b)CLS和非CLS异常
// 摘要: 
    //     包装不是从 System.Exception 类派生的异常。此类不能被继承。
    [Serializable]
    public sealed class RuntimeWrappedException : Exception
3.System.Exception类
Exception属性:Message,Data,Source,StackTrace,TargetSite,HelpLink,InnerException
方法是否内联: [MethodImpl(MethodImplOptions.NoInlining)]
4.FCL定义的异常类
// 定义有点混乱,设计之初因素
System.Exception,System.ApplicationException,System.SystemException
5.抛出异常
如果已知异常抛出具体异常 dynamic用法
6.定义自己的异常类
Exception
7.用可靠性换区开发效率
a)ExceptionTest Test2();
b)C:\Windows\System32\EseNt.dll(本地数据库存储电子邮件)
8.指导原则和最佳实践
a)善用finally
b)使用lock,锁会在finally中释放(编译器自动生成代码)
 使用using,会在finally中调用Dispose
 使用foreach 会在调用IEnumerator对象的Dispose
 定义析构方法 会在finally块中调用基类的Finalize
c)catch 要使用有度
d)隐藏实现细节 throw new Exception
9.未处理的异常
windows日志->应用程序
控制面板:控制面板\所有控制面板项\操作中心\问题详细信息
CES(Corrupted state Exceptions)异常,clr自身的bug,捕获不了下面异常:(下面字符全部是大写)
Exception_Access_Violation Exception_Stack_Overflow
Exception_Illegal_Instruction Exception_In_Page_Error
Exception_Invalid_Disposition Exception_Noncontinuable_Exception
Exception_Priv_Instruction Exception_Unwind_Consolidate


如果要捕获上面异常需要做下面处理:
在托管方法上注册System.Runtime.ExceptionService.HandleProcessCorruptedStateExceptionsAttribute
还要应用System.Security.SecurityCriticalAttribute
config中配置:legacyCorruptedStateExceptionPolicy  = true


上面配置可以将大多数异常转换为:System.Runtime.InteropServices.SEHException对象,但是
Exception_Access_Violation 转换为System.AccessViolationException
Exception_Stack_Overflow 转换为System.StackOverflowException
调用方法前可以用 (一般有递归方法调用)
// 确保剩余的堆栈空间足够大,可以执行一般的 .NET Framework 函数。
System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()
10.对异常进行调试
Visio studio 调试->异常 选项卡
11.异常处理的性能问题
性能监测器:perfmon.exe
TryParsexxx
12.约束执行区域(CER,constraind execution region)
异常恢复,异步异常(asynchronous exception)
RuntimeHelpers.PrepareConstrainedRegions();
[method:System.Runtime.ConstrainedExecution.ReliabilityContract(Consistency.WillNotCorruptState,Cer.Success)]
13.代码契约
code contract:前条件,后条件,对象不变性(Object Invariant)
// 摘要: System.Diagnostics.Contracts.Contract
    //     包含用于表示程序协定(如前置条件、后置条件和对象固定)的静态方法。
    public static class Contract


代码契约工具:visual studio 项目属性里面(暂未集成)
Code Contract Rewriter :CCRewrite.exe
Code Contract Reference Assembly Generator(CCRefGen.exe)单独创建契约程序集


// 第21章 自动内存管理(垃圾回收)
1.理解垃圾回收平台的基本工作原理
a)访问一个资源所需的具体步骤
1.调用IL指令 newobj,为代表资源的类型分配内存.在C#中使用new,编译器就会自动生成该指令
2.初始化内存,设置资源初始状态,使资源可用.类型的实例构造器负责设置该初始化状态
3.访问类型的成员(可根据需要反复)来使用资源
4.摧毁资源的状态进行清理.Dispose
5.释放内存.垃圾回收器独自负责这一步
b)值类型(含所有枚举类型),集合类型,String,Attribute,Delegate,Exception所代表的资源无需执行特殊的清理早错.
例如:只需摧毁对象的内存中维护的字符数组,一个String资源就会被完全清理.
另外,如果一个类型代表着(或包装着)一个非托管资源或者本地资源(比如文件,数据库连接,套接字,mutex,位图,图标等)
    那么在对象的内存准备回收时,必须执行一些资源清理代码.
c)CLR要求所有的资源都从托管堆(managed heap)分配
newobj指令会导致clr执行以下步骤:(NextObjPtr设为保留地址空间的基地址[普通区段,大对象堆])
1.计算类型(及其所有基类型)的字段所需要的字节数.
2.加上对象的开销所需的字节数.每个对象都有两个开销字段:一个是类型对象指针和一个同步块索引.
对于32位应用程序,这两个字段各需32位,所以每个对象要增加8字节.64位需要增加16字节
3.CLR检查保留区域是否能够分配对象所需的字节数,如有必要就提交存储.
2.垃圾回收算法
根:静态字段,方法参数,局部变量,CPU寄存器(值类型永远不会成为跟根)
标记->检查根->对象可达(标记)->压缩(compact)->确定0代是否满->回收
3.垃圾回收与调试
Timer类,造成对象过早的回收
TimerTest.Test();
4.使用终结操作来释放本地资源
1.终结器 Finalize (C++析构函数), ~SomeType(){/*这里会进入Finalize方法*/}
2.System.Runtime.ConstrainedExecution.CriticalFinalizerObject
// 摘要: 
//     确保派生类中的所有终止代码均标记为关键。
public abstract class CriticalFinalizerObject
3.System.Runtime.InteropServices.SafeHandle
// 摘要: 
//     表示操作系统句柄的包装类。必须继承此类。
[SecurityCritical]
public abstract class SafeHandle : CriticalFinalizerObject, IDisposable
4.Microsoft.Win32.SafeHandles;
SafeFileHandle,SafeHandleZeroOrMinusOneIsInvalid......等等.....
5.SafeHandle,可以保证在垃圾回收时,本地资源会得到释放
SafeHandleTest
5.对托管资源使用终结操作
GCBeep
6.什么会导致Finalize方法被调用
1.第0代满
2.代码显式调用GC.Collect()
3.windows报告内存不足
4.CLR卸载AppDomain
5.CLR关闭
7.终结揭秘
根,托管堆,终结列表,freachable队列 (System.Object 定义了一个 Finalize,但是需要重写,这个不能继承)
8.Dispose模式:强制对象清理资源
SafeHandle 的 Dispose 方法实现
9.使用实现了Dispose模式的类型
File处理 Stream 处理
10.C# using语句
MutexDemo
// 摘要: 
    //     一个同步基元,也可用于进程间同步。
    public sealed class Mutex : WaitHandle
11.一个有趣的依赖性问题
FileStream fs = new FileStream("data.dat",FileMode.Create);
StreamWriter sw = new StreamWriter(fs);
sw.Write("hi ... here");


// 不要方剂写下Close 调用
sw.Close(); // 注意:StreamWriter.Close 会关闭FileStream;// FileStream无需显式关闭
12.手动监视和控制对象的生存期
a)GC句柄(GC Handle table)
// 摘要: System.Runtime.InteropServices.GCHandle
//     提供用于从非托管内存访问托管对象的方法。
[ComVisible(true)]
public struct GCHandle

其中一个方法
[SecurityCritical]
        public static GCHandle Alloc(object value, GCHandleType type);
// 摘要: 
//     表示 System.Runtime.InteropServices.GCHandle 类可以分配的句柄的类型。
[Serializable]
public enum GCHandleType
{
// 摘要: 
//     此句柄类型用于跟踪对象,但允许回收该对象。When an object is collected, the contents of the System.Runtime.InteropServices.GCHandle
//     are zeroed.Weak references are zeroed before the finalizer runs, so even
//     if the finalizer resurrects the object, the Weak reference is still zeroed.
Weak = 0,
//
// 摘要: 
//     该句柄类型类似于 System.Runtime.InteropServices.GCHandleType.Weak,但如果对象在终结过程中复活,此句柄不归零。
WeakTrackResurrection = 1,
//
// 摘要: 
//     此句柄类型表示不透明句柄,这意味着无法通过此句柄解析固定对象的地址。可以使用此类型跟踪对象,并防止它被垃圾回收器回收。当非托管客户端持有对托管对象的唯一引用(从垃圾回收器检测不到该引用)时,此枚举成员很有用。
Normal = 2,
//
// 摘要: 
//     此句柄类型类似于 System.Runtime.InteropServices.GCHandleType.Normal,但允许使用固定对象的地址。这将防止垃圾回收器移动对象,因此将降低垃圾回收器的效率。使用
//     System.Runtime.InteropServices.GCHandle.Free() 方法可尽快释放已分配的句柄。
Pinned = 3,
}
b)垃圾回收器如果使用GC句柄表,下面是垃圾回收器的行为:
1.垃圾回收器标记所有可达对象,然后,垃圾回收器扫描GC句柄表;所有Normal,Pinned对象被看成根,同时标记这些对象(包括这些对象通过它们的字段引用的对象)
2.垃圾回收器扫描GC句柄表,查找所有Weak记录项.如果一个Weak记录项引用了一个未标记的随行,指针标识的就是一个不可达对象(垃圾),该记录项的指针值更改为null
3.垃圾回收器扫描终结列表.如果列表中的一个指针引用未标记的对象,指针标识就可是一个不可达对象,指针将从终结列表移至freachable队列.在这个时候,对象被标记,因为现在对象有变成可达的对象
4.垃圾回收器扫描GC句柄表,查找所有WeakTrackResurrection记录项.如果一个WeakTrackResurrection记录项未标记对象(它现在是由freachable队列中的一个记录项指向的一个对象),指针标识就是一个不可达的对象(垃圾),该记录项的指针值更改为null
5.垃圾回收器对内存进行压缩(compact),"挤出"不可达对象留出的内存"空洞",这其实是一个内存碎片整理的过程(特殊情况,垃圾回收器判断不值得压缩就会不压缩).Pinned对象是不会压缩(移动)的.
b)fixed语句,固定对象 FixedDmo
c)WeakReference
// 摘要: 
//     表示弱引用,即在引用对象的同时仍然允许垃圾回收来回收该对象。
[Serializable]
[ComVisible(true)]
public class WeakReference : ISerializable
13.对象复活
在终结的时候有对引用进行赋值:
// 请求系统调用指定对象的终结器,此前已为该对象调用 System.GC.SuppressFinalize(System.Object)。
GC.ReRegisterForFinalize(this);
14.代
总共2代,0 1 2,容量越来越大,而且根据情况会调整
GCNotification
15.用本地资源的其他垃圾回收功能
System.Runtime.InteropServices.HandleCollector
    // 摘要: 
    //     跟踪未处理的句柄,并在达到指定阈值时强制执行垃圾回收。
    public sealed class HandleCollector


参见 BigNativeResource
16.预测需求大量内存的操作能否成功
System.Runtime.MemoryFailPoint
// 摘要: 
     //     执行之前检查是否有足够的内存资源。此类不能被继承。
     public sealed class MemoryFailPoint : CriticalFinalizerObject, IDisposable


MemoryFailPointTest
17.编辑控制垃圾回收器
GC.Collect();
     GC.WaitForPendingFinalizers();
     GC.Collect();
// 上面代码没有必要调用,除非你确定你真的要调用
 
// 获取垃圾在第几代中
GC.GetGeneration(new object());
     GC.GetGeneration(new WeakReference(new object()));


 GCTest.Test();
18.线程劫持
为了规避垃圾回收是对象引用出现错乱
19.垃圾回收模式
a)工作站:并发回收的工作站,无并发回收器的工作站 (并发回收器[concurrent collector])








GCSettings.IsServerGC;
GCLatencyMode
b)服务器:GC托管堆分成几个区域(section)  
20.大对象
字节数 >=85000 视为大对象
大对象,直接放在 第2代中
21.监视垃圾回收
//
    // 摘要: 
    //     检索当前认为要分配的字节数。一个参数,指示此方法是否可以等待较短间隔再返回,以便系统回收垃圾和终结对象。
    //
    // 参数: 
    //   forceFullCollection:
    //     如果此方法可以在返回之前等待垃圾回收发生,则为 true;否则为 false。
    //
    // 返回结果: 
    //     一个数字,它是托管内存中当前所分配字节数的可用的最佳近似值。
    [SecuritySafeCritical]
    public static long GetTotalMemory(bool forceFullCollection);


//
    // 摘要: 
    //     返回已经对对象的指定代进行的垃圾回收次数。
    //
    // 参数: 
    //   generation:
    //     对象的代,将针对此代确定垃圾回收计数。
    //
    // 返回结果: 
    //     自启动进程以来已经对指定代进行的垃圾回收次数。
    //
    // 异常: 
    //   System.ArgumentOutOfRangeException:
    //     generation 小于 0。
    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
    [SecuritySafeCritical]
    public static int CollectionCount(int generation);


cmd Perfmon工具
CLR Profiler工具

// 第22章 CLR寄宿和AppDomain
1.CLR寄宿
寄宿(hosting)允许使应用程序都能利用clr功能.现在可以加载多个版本的clr,可以用ClrVer.exe检查
寄宿的优点:
a)可以用任何编程语言编写
b)代码在JIT编译后执行,所以速度很快(而不是一边解释一边执行)
c)代码使用垃圾回收避免内存泄露和损坏
d)代码在一个安全的沙箱中运行
e)宿主不必操心提供一个丰富的开发环境.宿主可利用现有的技术,包括语言,编译器,编辑器,调试器,profiler等
2.AppDomain
APPDomain的具体功能
1.一个appdomian中的代码创建的对象不能由另一个appdomain中的代码直接访问
2.appdomain可以卸载
3.appdomain可以单独保护
4.appdomain可以单独实施配置
跨越appdomain边界访问对象 AppDomainDemo
1.按引用封送(Marshal-by-Reference)的类型 [性能受影响,但影响不大]
2.按值封送(Marshal-by-value)的类型
3.完全能封送的类型
3.卸载Appdomain
1.clr挂起进程中执行过的托管代码的所有线程
2.clr检查所有线程栈,查看那些线程正在执行要卸载的那个appdomain中的代码,或者那些线程会在某个时候返回至要卸载的那个appdomain.
 在任何一个栈上,如果有准备卸载的appd.clr都会强迫抛出一个ThreadAbortException异常(同事恢复线程的执行).这导致线程展开(unwind)
 在展开的过程中遇到所有finally块中的内容,以执行资源清理代码.如果没有代码扑捉ThreadAbortException异常,clr会"吞噬"这个异常,线程
 会终止,但进程会继续运行
3.当第2步发现所有线程都离开appdomain后,clr遍历堆,为引用了"由已卸载的appdomain创建的对象"的每一个代理对象设置一个flag标识.对象不可用了
4.clr强制垃圾回收.finalize方法调用
5.clr恢复剩余所有线程的执行
4.监视AppDomain
会造成资源开销, AppDomain.MonitorEnable = true 后就不能关闭
AppDomainMonitor
5.AppDomain FirstChance异常通知
AppDomain 实例事件扑捉异常
//
    // 摘要: 
    //     在运行时进行其第一遍搜索来查找合适的异常处理程序之前,在托管代码中首次出现异常时发生。
    public event EventHandler FirstChanceException;
6.宿主如何使用APPDomain
1.可执行应用程序
2.Microsoft silverlight 富Internet应用程序
3.Microsoft ASP.NET WEB 窗体和XML web服务应用程序
4.Microsoft SQL Server
5.更多的用法只限于你自己的想象力
7.高级宿主控制
1.使用托管代码管理CLR System.AppDomainManager
2.编写健壮的宿主应用程序
托管代码出现错误时,宿主可以告诉CLR采取什么行动(按验证从低到高)
1.如果一个线程的执行时间过长,clr可以终止线程并返回一个响应
2.clr可以卸载appdomain.这会终止该appdomain中的所有线程,导致有问题的代码卸载
3.clr可以被禁用.这会阻止更多的托管代码在程序中运行,但仍然允许非托管代码运行
4.clr可以退出windows进程.首先会终止所有线程,并卸载所有appdomain,使资源清理得以执行,然后才会终止进程
3.宿主应用程序如何拿回它的线程
1.一个客户端向服务器发送一个请求
2.一个服务器线程获得该请求,把它派遣给一个线程池线程来执行实际工作
3.线程池线程获得客户端的请求,执行由构建并测试宿主应用程序的那个公司写的可信代码(trusted code)
4.可信代码进入一个try块.从这个try块中,跨越一个appdomain的边界进行调用(通过派生自MarshalByRefObject的一个类型).这个appdomain包含了不可信的代码(可能是存储过程),
 这些代码不是由制作宿主的应用程序的那个公司生成和测试的.在这个时候,服务器相当于将它的线程的控制权交给了一些不可信的代码;服务器开始感到有点儿"紧张"了.
5.宿主最初接收到客户端的请求时,会记录时间.如果不可信代码在 管理员设定的一个时间期限内没有对客户端做出响应,宿主就会调用Thread的Abort方法要求clr终止线程池线程,强制它抛出一个ThreadAbortException
6.这时,线程池开始展开(unwind),调用finally块,使清理代码得以执行.最终,线程池穿越了appdomain边界返回.由于宿主的存根代码是从一个try模块中调用的可信代码,所以宿主的存根代码有一个catch块扑捉ThreadAbortException
7.为了响应捕捉到的ThreadAbortException异常,宿主调用Thread的ResetAbort方法
8.现在宿主的代码已扑捉到ThreadAbortException异常.因此,宿主可以向客户端返回某种形式的错误,允许线程池线程返回线程池,供未来的客户端请求使用

// 第23章 程序集加载和反射
1.程序集加载
JIT编译器利用程序集的TypeRef和AssemblyRef元数据来确定哪一个程序集定义了所引用的类型
System.Reflection.Assembly
.Load(GAC全局程序集缓存,应用程序的基目录,私有路径子目录,codebase),可以传递一个URL

// 嵌入资源AppDomain 利用 ResolveAssembly事件加载dll
internal static void Test()
    {
        AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
        {
            String resourceName = "AssemblyLoadingAndReflection." + new AssemblyName(args.Name).Name + ".dll";


            using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
            {
                byte[] assemblyData = new byte[stream.Length];
                stream.Read(assemblyData, 0, assemblyData.Length);
                return Assembly.Load(assemblyData);
            }
        };
    } 
2.使用反射构建动态可扩展性应用程序
序列化格式器(serialization formatter)
晚起绑定(late binding)
3.反射的性能
缺点:
1.反射会造成编译时无法保证类型安全性
2.反射速度慢
改进:
1.利用基类
2.利用接口
1.发现程序集中定义的类型(AssemblyResolveTest.FindGetExportedTypes();)
Assembly.GetExportedTypes
2.类型对象的准确含义 (AssemblyResolveTest.Test2();)
typeof(早期绑定),Type(晚期绑定),MethodInfo...
Backus-Naur Form,BNF语法(巴克斯-诺尔范式)
3.构建Exception派生类型的一个层次 (AssemblyResolveTest.Exceptions();)
4.构造类型的实例
System.Activator.CreateInstance
System.Activator.CreateInstanceFrom
System.AppDomain.Create....
System.Type.InvokeMember
System.Reflection.ConstructorInfo.Invoke
4.设计支持加载项的应用程序
1.加载dll(接口)
2.实现dll中接口,找到方法
3.反射执行方法
5.使用反射发现类型的成员 ReflectionTest
1.发现类型成员
2.BindingFlags:筛选返回的成员种类
3.发现类型的接口
4.调用类型成员(CAS,Code Access Security,代码访问安全性)
5.一次绑定,多次调用
6.使用绑定句柄来减少进程的内存耗用


// 第24章 运行时序列化
序列化(serialization)是将一个对象或者对象图转换成一个字节流的过程.
反序列化(deserialization)是将一个字节流换回对象图的过程.
1.序列化/反序列化快速入门
QuickStart
DeepClone
2.使类型可序列化
System.Runtime.InteropServices.SerializableAttribute
3.控制序列化和反序列化 Circle
System.NonSerializedAttribute
[OnDeserializing],[OnDeserialized],[OnSerializing],[OnSerialized]
4.格式化器如何序列化类型实例
System.Runtime.Serialization.FormatterServices
// 摘要: 
    //     获取指定 System.Type 的类的所有可序列化成员。
[SecurityCritical]
        public static MemberInfo[] GetSerializableMembers(Type type);
// 摘要: 
     //     获取属于指定 System.Type 的类且位于提供的 System.Runtime.Serialization.StreamingContext
     //     中的所有可序列化成员。
[SecurityCritical]
        public static MemberInfo[] GetSerializableMembers(Type type, StreamingContext context);


FCL如何自动化序列化一个(其类型)应用了SerializableAttribute 的对象
1.格式化器调用 FormatterServices的GetSerializableMembers方法
public static MemberInfo[] GetSerializableMembers(Type type, StreamingContext context);
,返回MemberInfo[],其中每个元素都对应一个可序列化的实例字段
2.对象被序列化,System.Reflection.MemberInfo对象数组传给FormatterServices的GetObjectData
public static object[] GetObjectData(object obj, MemberInfo[] members);
返回一个object[],这个数组跟MemberInfo 一一对应,并且已经标识了字段的值
3.格式化器将程序集标识和类型的完整名称写入流中
4.格式化器然后遍历两个数组中的元素,将每个成员的名称和值写入流中

反序列化流程:
1.格式化器调用从流中读取程序集标识和完整类型名称.如果当前程序集没有加载到appdomain就加载.不能加载,抛出异常,可以序列化
调用FormatterServices的GetTypeFromAssembly
public static Type GetTypeFromAssembly(Assembly assem, string name);
返回类型代表要反序列化的那个对象
2.格式化器调用 FormatterServices的GetUninitializedObject
public static object GetUninitializedObject(Type type);
该方法为一个新的对象分配内存,并不为对象调用构造器,然而,对象的所有字节都被初始化成null或0
3.格式化器现在构造并初始化一个MemberInfo数组,具体调用FormatterServices中GetSerializableMembers.
这个方法返回序列化好,现在需要反序列化的一组字段
4.格式化器根据流中包含的数据创建并初始化一个object数组
5.将对新分配的对象,MemberInfo数组以及并行object数组(其中包含字段值)的引用传给FormatterServices的静态方法PopulateObjectMembers
public static object PopulateObjectMembers(object obj, MemberInfo[] members, object[] data);
这个方法遍历数组,将每个字段初始化成对应的值.到此为止,对象被彻底反序列化.
5.控制序列化/反序列化的数据
    // 摘要: 
    //     允许对象控制其自己的序列化和反序列化过程。
    [ComVisible(true)]
    public interface ISerializable
// 摘要: 
    //     指示在完成整个对象图形的反序列化时通知类。
    [ComVisible(true)]
    public interface IDeserializationCallback


使基类的字段能被序列化:Derived
6.流上下文
// 摘要: 
    //     描述给定的序列化流的源和目标,并提供一个由调用方定义的附加上下文。
    [Serializable]
    [ComVisible(true)]
    public struct StreamingContext


// 摘要: 
    //     定义一个标记集,用于在序列化过程中指定流的源或目标上下文。
    [Serializable]
    [ComVisible(true)]
    [Flags]
    public enum StreamingContextStates


7.将类型序列化为不同类型以及将对象序列化为不同的对象
Singleton
8.序列化代理
System.Runtime.Serialization.ISerializationSurrogate


// 摘要: 
    //     实现序列化代理项选择器,此选择器允许一个对象对另一个对象执行序列化和反序列化。
    [ComVisible(true)]
    public interface ISerializationSurrogate

SurrogateSelector.AddSurrogate 代码选择器链接
9.反序列化一个对象时重写程序集和/或类型
System.Runtime.Serialization.SerializationBinder
    // 摘要: 
    //     允许用户控制类加载并指定要加载的类。
    [Serializable]
    [ComVisible(true)]
    public abstract class SerializationBinder


Ver1ToVer2SerializationBinder
// 第25章 线程基础
1.windows为什么要支持线程
进程(process),线程(thread)
2.线程开销
每个线程中的要素:
1.线程内核对象(thread kernel Object);x86线程上下文使用约700字节,x64和IA64cpu上下文约使用1240字节和2500字节的内存
2.线程环境块(thread environment block,TEB),TEB耗用1个内存页(x86,x64是4kb,IA64cpu是8kb),TEB还包含线程的"线程本地存储"数据以及GDI(graphics Device Interface,图形设备接口)和OpenGL图形使用的一些数据结构
3.用户模式栈(user-mode stack).默认分配1mb内存
4.内核模式栈(kernel-mode stack),32位,12kb;64位上kb
5.dll线程连接(attach)和线程分离(detach)通知  DllMain,DLL_THREAD_DETACH
上下文切换windows执行的操作
1.将cup寄存器的值保存到当前正在运行的线程的内核对象内部的一个上下文结构中
2.从现有线程集合中选出一个线程供调度(这个线程要切换到的线程).如果该线程由另外一个进程拥有,windows在开始执行任何代码或者接触任何数据之前,还必须切换cpu"看见"的虚拟地址空间
3.将所选上下文结构中的值加载到cpu寄存器
3.停止疯狂
windows资源管理器:性能,进程(线程数),windows创建大量的进程(线程),很多都没用
4.cpu发展趋势
多个cpu,超线程芯片,多核芯片
5.muma架构的机器
NUMA(Cache-Coherent Non-Uniform Memory Access):非统一内存访问架构
6.clr线程和windows线程
7.使用专用线程执行异步的计算限制操作
前台线程和后台线程(线程池的线程始终是后台线程),创建线程默认是前台线程
*.满足下面任何一个条件就可以显式创建自己的线程
1.线程需要以非普通线程优先级运行.
2.需要线程表现为一个前台线程,防止应用程序在线程结束它的任务之前终止.
3.一个计算限制的任务需要长时间运行
4.要启动一个线程,并可能调用Thread的Abort方法来提前终止
8.使用线程的理由
可以使用线程将代码同其他代码隔离,可以使用线程简化编码,可以使用线程来实现并发执行
9.线程调度和优先级
1.抢占式(preeminent)
2.Spy++工具
3.零页线程(zero page thread),优先级为0
4.windows的6个进程优先级类 Idel,Below Normal,Normal,Above Normal,High,Realtime
5.windows支持7个相对线程优先级:Idel,Lowest,Below Normal,Normal,Above Normal,Highest,Time-Critical
6.进程优先级类 相对线程优先级 优先级关系(0-31)
没有优先级为0 ,17,18,19,20,21,27,28,29,30的
10.前台线程和后台线程
ThreadBackground
11.继续学习
http://wintellect.com
http://wintellect.com/powerthreading.aspx

// 第26章 计算限制的异步操作
0.计算限制
编译代码,拼写检查,语法检查,电子表格重计算,音频或视频数据转码以及生产图像的缩略图.
1.clr线程池基础
线程池将自己的线程划分为工作者(Worker)线程或I/O线程.
异步编码模型(Asynchronous Programming Model,APM)(I/O请求),比如(访问文件,网络服务器,数据库,web服务或其他硬件设备)
2.执行简单的计算限制操作
ThreadPoolDemo
// 摘要: 
     //     提供一个线程池,该线程池可用于发送工作项、处理异步 I/O、代表其他线程等待以及处理计时器。
     public static class ThreadPool
3.执行上下文
ExecutionContextDemo
    // 摘要: 
    //     管理当前线程的执行上下文。此类不能被继承。
    [Serializable]
    public sealed class ExecutionContext : IDisposable, ISerializable
4.协作式取消
System.Threading.CancellationTokenSource
    // 摘要: 
    //     通知 System.Threading.CancellationToken,告知其应被取消。
    [ComVisible(false)]
    public sealed class CancellationTokenSource : IDisposable


System.Threading.CancellationToken
// 摘要: 
    //     传播有关应取消操作的通知。
    [ComVisible(false)]
    [DebuggerDisplay("IsCancellationRequested = {IsCancellationRequested}")]
    public struct CancellationToken


CancellationDemo
5.任务
System.Threading.Tasks


System.Threading.Tasks.TaskCreationOptions
/ 摘要: 
    //     指定可控制任务的创建和执行的可选行为的标志。
    [Serializable]
    [Flags]
    public enum TaskCreationOptions
TaskDemo
1.等待任务完成并获取它的结果
2.取消任务
3.一个任务完成时自动启动一个新的任务
TaskContinuationOptions
4.任务可以启动子任务
TaskCreationOptions.AttachedToParent
5.任务内部揭秘
// 摘要: System.Threading.Tasks.TaskStatus
//     表示 System.Threading.Tasks.Task 的生命周期中的当前阶段。
public enum TaskStatus
6.任务工厂
System.Threading.Tasks.TaskFactory
// 摘要: 
//     提供对创建和计划 System.Threading.Tasks.Task 对象的支持。
//
// 类型参数: 
//   TResult:
//     可通过与此类中的方法相关联的 System.Threading.Tasks.Task{TResult} 对象获得的结果的类型。
public class TaskFactory
7.任务调动器
System.Threading.Tasks.TaskScheduler
/ 摘要: 
//     表示一个处理将任务排队到线程中的低级工作的对象。
[DebuggerDisplay("Id={Id}")]
[DebuggerTypeProxy(typeof(TaskScheduler.SystemThreadingTasks_TaskSchedulerDebugView))]
public abstract class TaskScheduler
6.parallel的静态for,foreach,和invoke方法
System.Threading.Tasks.Parallel
    // 摘要: 
    //     提供对并行循环和区域的支持。
    public static class Parallel


ParallelDemo
7.并行语言集成查询(PLNQ)
.AsParallel()
http://blogs.msdn.com/b/pfxteam/archive/2009/05/28/96487672.aspx
http://blogs.msdn.com/b/pfxteam/archive/2009/06/13/9741072.aspx
8.执行定时计算限制操作
Timer
太多的计时器,太少的时间
1.System.Threading的Timer类:要在一个线程池线程上执行定时(周期性发生的)后台任务,他是最好的计时器
2.System.Windows.Form的Timer类,windows系统调用一个计时器和调用线程关联.这个计时器方法不会由多个线程并发执行
3.System.Windows.Threading的DispatcherTimer类,这个类是2中的等价物,用在silverlight和wpf中
4.System.Timers的Timer类,这个计时器基本上是System.Threading.Timer类的一个包装类(最好不要用),这个可以在设计平面中添加
9.线程池如果管理线程
1.设置线程池限制
 //System.Threading.ThreadPool.SetMaxThreads(1, 11);
          //System.Threading.ThreadPool.GetMaxThreads();
          //System.Threading.ThreadPool.SetMinThreads(1, 11);
          //System.Threading.ThreadPool.GetMinThreads();
2.如果管理工作者线程
ThreadPool.QueueUserWorkItem和Timer类总是将工作项放到全局队列中.
工作者线程采用先入先出(FIFO)算法
10.缓存线和伪共享
FalseShareing

// 第27章 I/O限制的异步操作
1.windows如何执行I/O操作
同步I/O 异步I/O
2.clr的异步编程模型(APM)
异步编程模型(Asynchronous Programming Model,APM)
System.IO.Stream
System.Net.Dns
System.Net.Sockets.Socket
System.Net.WebRequest
System.IO.Ports,SerialPort
System.Data.SqlClient.SqlCommond
BeginXxx EndXxx
3.AsyncEnumerator类
APM的问题
1.必须将代码分解成多个回调方法
2.必须避免使用实参和局部变量,因为这些变量在一个线程栈中分配,不能由另一个线程或者另一个方法访问
3.许多C#语言构造 try/catch/finally,using,for,do,while.foreach
4.很难实现许多开发人员需要的其他一些功能,比如多个并发操作的协作进行,支持取消和超时,以及将工作封送给GUI线程以更新控件
AsyncEnumerator: http://www.wintellect.com/ http://wintellect.com/powerthreading.aspx
4.APM和异常
ApmExceptionHanding
5.应用程序及线程处理模型
SynchronizationContext
SyncContextCallback
6.异步实现服务器
web页面,Page 指令 Async=true
webservice [WebMethod]
WFC[OperationContract(AsyncPattern=true)]
7.APM和计算限制操作
AsyncDemo
8.APM注意事项
1.在没有线程池的前提下使用APM
2.总是调用EndXxx方法,而且只调用一次
3.调用EndXxx方法时总是使用相同的对象
4.为BeginXxx 和 EndXxx方法使用 ref out params实参
5.不能取消异步I/O限制操作
6.内存消耗
7.有的I/O操作必须同步完成
8.FileStream特有的问题 (windows底层系统问题)
9.I/O请求优先级
P/Invoke 代码
(http://www.microsoft.com/whdc/driver/priorityio.mspx
https://msdn.microsoft.com/en-US/library/windows/hardware/dn550976)
ThreadIO
10.将IAsyncResult APM转换为Task
AsyncResultAPMToTask
11.基于事件的异步模式
Event-based Asynchronous Pattern,EAP 基于时间的异步模式
https://msdn.microsoft.com/zh-cn/library/ms228966.aspx
1.将EAP转换为Task System.Threading.Tasks.TaskCompletionSource
// 摘要: 
    //     表示未绑定到委托的 System.Threading.Tasks.Task{TResult} 的制造者方,并通过 System.Threading.Tasks.TaskCompletionSource.Task
    //     属性提供对使用者方的访问。
    //
    // 类型参数: 
    //   TResult:
    //     与此 System.Threading.Tasks.TaskCompletionSource 关联的结果值的类型。
    public class TaskCompletionSource
EapDemo
2.APM和EAP的对比
12.编程模型的泥沼
比较.net framework的异步编程模型


模型 主要用途 用什么模拟辅助用途 父/子 进度报告 取消 等待 超时 返回结果异常
QueueUser/WorkItem 计算 同步I/O  NO NO NO (no)(no)(no)
Timer 计算 同步I/O  NO NO 通过Dispose (no)(yes!)(no)
RegisterWaitFor-/SingleObject 计算 同步I/O  NO NO 通过Unregister(no)(yes)(no)
Tasks 计算 同步I/O或者Task-CompletionSource和TaskScheduler和FromAsync(Yes)(NO)(计算:在task开始之前取消,或者加入task支持取消.I/O:放弃结果)(Yes)(Yes)(Yes)
IAsyncResultAPM I/O 委托的BeginInvoke  NO NO No (yes)(no)(yes)
EAP(基于事件) I/O BackgroundWorker  NO 部分 部分类型放弃结果(no)(no)(yes)
AsyncEnumerator I/O 委托的BeginInvoke      NO NO Yes (no)(yes)(yes)


1.如果由于工作窃取(work-stealing)队列而发生了大量任务,那么Task提供了比ThreadPool.QueueUserWorkItem或者一个委托的BeginInvoke更好的性能
2.可以使用PreferFairness标志,获取与ThreadPool.QueueUserWorkItem或者一个委托的BeginInvoke相同的线程池行为
3.可用一个自定义的TaskScheduler 更改调度算法,同时不更改或编程模型
4.Task对象消耗的内存比调用ThreadPool.QueueUserWorkItem或者一个委托的BeginInvoke方法要多一些.调用一个委托的BeginInvoke方法存在已知的性能问题.
 虽然Task对象需要更多的内存,但任务运行得更快,可能是比调用一个委托得BeginInvoke方法更好得选择
5.IAsyncResult APM提供4种约会技术,这使模型变得复杂化.然而,如果坚持使用回调方法技术,这个模型黑阿辉很简单的.
6.和EAP相比IAsyncResult APM通常更快,而且使用更少的资源
7.支持EAP的一些类提供了对取消的支持
8.IAsyncResult APM完全不支持取消,但是,可以设置一个标志,并在完成时丢弃结果,从而模拟取消行为.将IAsyncResult模式包装到一个Task中,并设置正确的ContinueWith回调,可以在一定程度上帮到你.
9.EAP是基于事件的,所以可以在Visual Studio的Windows Forms、WPF和Silverlight窗体设计器中方便地使用它.另外,通知方法也会在正确的UI线程中得到调用.

// 第28章 基元线程同步构造
1.类库和线程安全
Microsoft的Framework Class Library(FCL)保证所有的静态方法都是线程安全的
2.基元用户模式和核心模式构造
基元(primitive):用户模式(user-mode)和内核模式(kernel-mode)
应尽量使用基元用户模式构造,它们的速度要显著快于内核模式构造,这是因为它们使用了特殊CPU指令来协调线程,这意味着协调在硬件中发生.
3.用户模式构造
1.易失构造(volatile construct),它包含一个简单数据类型的变量上执行原子性的读 或 写操作
2.互锁构造(interlocked construct),它包含一个简单数据类型的变量上执行原子性的读 和 写操


内存地址正确对齐,编译器自动优化代码,volatile关键字
3.实现简单的Spin Lock
 Thread.Sleep(11);
 // 参数
1.-1,告诉系统永远不调度线程
2. 0,告诉系统调用线程放弃了它当前的时间片剩余部分
3. 1,总是强迫进行一次上下文切换,而由于内部系统计时器的解析度问题,windows总是强迫线程睡眠超过1毫秒的时间


 //     导致调用线程执行准备好在当前处理器上运行的另一个线程。由操作系统选择要执行的线程。
          Thread.Yield(); // 介于调用Thread.Sleep(0) 和 Thread.Sleep(1)之间
          //     导致线程等待由 iterations 参数定义的时间量。
 Thread.SpinWait(22);
4.Interlocked Anying 模式
Maximum,Morph(会导致性能的惩罚)
4.内核模式构造
内核模式的构造具有基元用户模式构造所不具有的一些优点:
1.一个内核模式的构造监测到在一个资源上的竞争时,windows会阻塞输掉的线程,使它不占这一个CPU"自旋"(spinning),无谓地浪费处理器资源
2.内核模式的构造可实现本地(native)和托管(managed)线程相互之间的同步
3.内核模式的构造可同步在一台机器的不同进程中运行的线程
4.内核模式的构造可应用安全性设置,防止未经授权的账户访问它们.
5.一个线程可一直阻塞,直到一个集合的所以后内核模式的构造都可用,或者直到一个集合中的任何一个内核模式的构造可用
6.在内核模式的构造上阻塞的一个线程可以指定一个超时值;如果在指定的时间内访问不希望的资源,线程可以解除阻塞并执行其他任务.
WaitHandle
EventWaitHandle
AutoResetEvent
ManualRestEvent
Semaphore
Mutex
1.Event构造
SimpleWaitLock
2.Semaphore构造
3.Mutex构造
4.在一个内核构造可用时调用一个方法

// 第29章 混合线程同步构造
hybrid Thread synchronization construct 混合同步构造
http://wintell.com
1.一个简单的混合锁
SimpleHybridLock
2.自旋,线程所有权和递归
AnotherHybridLock
3.混合构造的大杂烩
1.ManualResetEventSlim类和SemaphoreSlim
2.Monitor同步块
3.ReaderWriterLockSlim
// 摘要: System.Threading.ReaderWriterLockSlim
//     表示用于管理资源访问的锁定状态,可实现多线程读取或进行独占式写入访问。
public class ReaderWriterLockSlim : IDisposable
4.OneManyLock类
http://wintellect.com
5.CountdownEvent类
// 摘要: System.Threading.CountdownEvent
//     表示在计数变为零时处于有信号状态的同步基元。
[ComVisible(false)]
[DebuggerDisplay("Initial Count={InitialCount}, Current Count={CurrentCount}")]
public class CountdownEvent : IDisposable
6.Barrier类
// 摘要: System.Threading.Barrier
//     使多个任务能够采用并行方式依据某种算法在多个阶段中协同工作。
[ComVisible(false)]
[DebuggerDisplay("Participant Count={ParticipantCount},Participants Remaining={ParticipantsRemaining}")]
public class Barrier : IDisposable
7.线程同步构造小结
以下两种方式才考虑阻塞线程: 线程模型很简单,线程有专门的用途
4.著名的双检索技术
双检锁(Double-Check Locking)是一种非常著名的技术,开发人员用它一个单实例(singleton)对象构造推迟到一个应用程序首次请求这个对象的时候进行.
有时候也称为延迟初始化(lazy initialization)
http://www.cs.umd.edu/
http://www.cs.umd.edu/~pugh/java/memoryModel/doublecheckedlocking.html


Singleton
5.条件变量模式
条件变量(condition variable)


ConditionVariablePattern
SynchronizedQueue
6.用集合防止占有锁太长的时间
使用任务Task具有的优势
1.任务使用的内存比线程少得多,创建和销毁所需的时间也少的多.
2.线程池根据可用CPU数量自动伸缩任务规模
3.每个任务完成一个阶段后,运行任务的线程回到线程池,以便在那里接受新任务
4.线程池是站在整个进程的高度观察任务.所以,它能更好的调度这些任务,减少进程中的线程数,并减少上下文切换.
7.并发集合类
// 并发集合类
    //System.Collections.Concurrent.ConcurrentQueue<>( mscorlib.dll)
    //System.Collections.Concurrent.ConcurrentStack<>( mscorlib.dll)
    //System.Collections.Concurrent.ConcurrentDictionary( mscorlib.dll)
    //System.Collections.Concurrent.ConcurrentBag<> (system.dll)

你可能感兴趣的:(读书笔记)