1. 界面之间的传值更多的使用属性,而不是公用变量 2. 运行时常量(readonly)和编译时常量(const) 的区别 运行时常量:系统引用的是变量,修改时只需要编译常量定义的地方 编译时常量:系统引用的是值,修改时要进行项目全编译 3. 推荐使用 is 或 as 操作符而不是强制类型转换 object o=Factory.GetObject(); MyType t=o as MyType; if (t==null) { // 说明两者之间不能进行转换 } else { // 说明两者之间可以进行转换 } cast 操作符 和 as 操作符之间最大的区别就在于如何处理用户自定义的转换 as 和 is 操作符都只检查被转换对象的运行时类型,并不执行其它操作. object o=Factory.GetObject(); int i=0; if (o is int) i=(int)o; 仅当不能使用 as 进行转换时,才应该使用 is 操作符.否则,is 就是多余的. 4. 使用 Conditional 特性而不是 #if 条件编译 #if / #endif 语句常用来基于同一份源代码生成不同的编译结果,其中最常见的就是 debug 版和 release 版. Conditional 特性可以标识出某种环境设置下某个方法是否应该被调用. [Conditional("DEBUG"),Conditional("TRACE")] // 在 DEBUG 或者 TRACE 方式下进行调用 或方式 private void CheckState() { // same code as above } 应用了Conditional特性之后,C#编译嚣只有在检测到定义了DEBUG环境变量时才会对CheckState()进行调用. Conditional特性不会影响对CheckState()方法的编译,它只会影响对该方法的调用. Conditional特性只可以应用在整个方法上,另外需要注意的是,任何一个使用Conditional特性的方法都只能返回 void 类型,一般也不支持其有参数 注:debug版--调试版 release版--发布版,拥有优化的代码与运行速度 5. 为类型提供 ToString() 方法 public override string ToString() { return 一个值; } 6. 理解几个等同性判断之间的关系 C# 提供了4种不同的函数来判断两个对象是否"相等": public static bool ReferenceEquals (object left,object right); public static bool Equals (object left,object right); public virtual bool Equals (object right); public static bool operator == (MyClass left,MyClass right); C# 允许我们创建两种类型:值类型和引用类型.如果两个引用类型的变量指向的是同一个对象,它们将被认为是"引用相等", 如果两个 值类型 的变量类型相同,而且包含同样的内容,它们被认为是"值相等". Equals()实例的覆写规则:对于所有的值类型,都应该覆写其 Equals() 方法;对于引用类型,当 System.Object 提供的引用语义不能满足我们 的需要时,才应该去覆写 Equals() 方法.此外,在覆写 Equals()方法的同时也要覆写GetHashCode()方法. 7. 理解 GetHashCode() 的陷阱 此函数仅会在一个地方用到,即为基于散列(hash)的集合定义键的散列值时,此类集合包括HashSet<T>和Disctionary<K,V>容器等. 8. 推荐使用查询语法而不是循环 查询语法定义了想要的结果,而把如何得到这些结果的任务交给了其他的专门实现. int[] foo =new int[100]; for (int num=0;num<foo.Length;num++) foo[num]=num*num; foreach (int i in foo) Console.WriteLine(i.ToString(); 第一步可以将生成数组的工作交给一个查询完成: int[] foo=(from n in Enumerable.Range(0,100) select n *n).ToArray(); 第二步打印输出: foo.forAll((n) => Console.WriteLine(n.ToString())); 9. 避免在API中使用转换操作符 转换操作符为类之间引入了一种"可替换性".可替换性"表示一个类的实例可以被替换为另一个类的实例. 10. 使用可选参数减少方法重载的数量 11. 理解短小方法的优势 更小的函数一般将包含更少的局部变量,也就更方便JIT对寄存器进行优化 第二章 .NET 资源管理 GC 原理的介绍 12. 推荐使用成员初始化器而不是赋值语句 三种情况避免使用初始化器语法 (1).当你想要初始化对象为 0 或 null 时.系统默认的初始化工作将在执行所有代码之前把一切都设置成 0 或 null. (2).当你需要对同一个变量执行不同的初始化方式. (3).初始化器无法用try包裹.对象初始化器执行的过程中发生的所有异常都会传递到对象之外. 成员初始化器是保证类型中成员变量均被初始化的最简单方法----无论调用的是哪一个构造函数. 初始化器将在所有构造函数执行之前执行. 13. 正确地初始化静态成员变量 静态构造函数是一个特殊的函数,将在其他所有方法执行之前以及变量或属性被第一次访问之前执行. 使用静态构造函数而不是静态初始化器的最常见理由就是处理异常. static MySingleton2() { try { theOneAndOnly = new MySingleton2(); } catch { // 输出异常 } } 14. 尽量减少重复的初始化逻辑 保证在构造的过程中对每个变量仅初始化一次. (1).静态变量设置为0; (2).执行静态变量初始化器. (3).执行基类的静态构造函数. (4).执行静态构造函数. (5).实例变量设置为0. (6).执行实例变量初始化器. (7).执行基类中合适的实例构造函数. (8).执行实例构造函数. 15. 使用 using 和 try/finally 清理资源 使用了非托管系统资源的类型必须地使用 IDisposable 接口的Dispose() 来释放. 当你在 using 语句中分配对象,C# 编译器将自动在每个对象外生成一个try/finally 块; SqlConnection myConnection=null; using (myConnection = new SqlConnection(connString)) { } /////////// 等价 ////////////////// try { myConnection new SqlConnection(connString) myConnection.Open(); } catch (Exception) { throw; } finally { myConnection.Dispose(); } 仅在编译期类型支持 IDSposable 接口时,你才能使用 using 语句.对于编译期类型无法确定的对象,则不能这样做; 若你无法确定是否应该用 using 包裹某个对象,那么就使用 as 关键字 : using (obj as IDisposable) obj.方法名(); 16. 避免创建非必要的对象 若是在某个方法中创建了太多的引用对象,那么将会对程序的性能产生严重的影响. 所有的引用类型,包括那些局部变量,都会分配在堆上.在函数退出之后,函数内的所有局部变量都会立即变成垃圾. 若是某个引用类型(值类型则无所谓)的局部变量用于将被频繁调用的例程中,那么应该将其提升为成员变量. 提供一个类,存放某个类型常用实例的单例对象(静态对象等). 直接创建不可变类型的最终值. System.String 类就是不可变的,在构造好一个字符串之后,其内容不能被修改. string str="abc"; str += "def"; str += "ght"; 这些会产生大量的垃圾变量,应改为: string str=string.Format("{0},{1},{2}","abc","def","ght"); 或者直接创建一个 StringBuilder 类型的变量 17. 实现标准的销毁模式 若你的类使用了非托管资源,那么必须提供一个终结器. 在GC运行时,它会立即清理掉那些没有提供终结器的垃圾对象.而提供了终结器的垃圾对象会停留在内存中,被添加到 一个叫做"终结队列"的地方.GC会使用另一个线程来执行队列中对象的终结器. IDisposable.Dispose() 方法中实现中需要完成如下4个任务: (1) 释放所有非托管资源 (2) 释放所有托管资源,包括释放事件监听程序. (3) 设定一个状态标志,表示该对象已经被销毁.若是在销毁后再次调用对象仅有方法,那么应抛出ObjectDisposed异常. (4) 跳过终结操作,调用 GC.SuppressFinalize(this) 即可. protected virturl void Dispose(bool isDisposing); 派生类可以覆写该方法,在其中清理其自身的资源,然后调用基类的版本.在isDisposing为true时,清理托管和非托管资源, 在isDisposing为false时,仅清理非托管资源. 无论isDisposing取值如何,都要调用基类的Dispose(bool)方法,以便让基类完成自身资源的释放. 只能释放资源,不得在Dispose方法中执行任何别的操作. 18 区分值类型和引用类型 值类型无法实现多态,因此其最佳用途就是存放应用程序中用到的数据. 引用类型支持多态,因此可用来定义应用程序的行为. 结构用来存放数据,类用来定义行为. 用值类型表示底层存储数据的类型,用引用类型来封装程序的行为. 19. 保证0为值类型的有效状态 .NET系统的默认初始化过程会将所有的对象设置为0. 20. 保证值类型的常量性和原子性 在这些类型的基础上,即可很容易地构建更复杂的结构. 21. 限制类型的可见性 在保证类型可以完成其工作的前提下,你应该尽可能地给类型分配最小的可见性. 22. 通过定义并实现接口替代继承 接口是一种按契约设计的方式,一个实现某个接口的类型,必须实现接口中约定的方法. 抽象基类则为一组相差的类型提供了一个共用的抽象. 继承意味着"是什么",接口意味着"表现行为": 基类描述了对象是什么,接口描述了对象将如何表现其行为. 接口中不能提供任何成员的实现. 在抽象基类和接口之间做选择,实际上就表示了对日后可能发生变化的不同处理态度: 接口是固定的: 我们将一组功能封闭在一个接口中,作为其它类型的实现契约. 基类则可以在日后进行扩展,这些扩展也会成为每个派生类的一部分. 23. 理解接口方法和虚方法的区别 interface IMsg { void Message(); } public class MyClass:IMsg { public void Message() { Console.WriteLine("MyClass"); } public virtual void Messagebox() // 将接口方法声明为虚方法 { Console.WriteLine("MyClass"); } } public class MyDerivedClass:MyClass { public new void Message() // 这里不是覆写基类的Message()方法,而是创建了一个新的Message()方法 { Console.WriteLine("MyDerivedClass"); } public override void Messagebox() // 覆写父类的虚方法. { Console.WriteLine("MyDerivedClass"); } } MyClass MC = new MyClass(); MC.Message(); // MyClass MC.Messagebox(); // MyClass MyDerivedClass MD = new MyDerivedClass(); MD.Message(); // MyDerivedClass MD.Messagebox(); // MyDerivedClass MyClass CC = MD as MyClass; CC.Message(); // MyClass CC.Messagebox(); // MyDerivedClass Console.ReadLine(); 24. 用委托实现回调 回调: 可为服务器和客户机之间提供异步的反馈. 委托为我们提供了类型安全的回调定义. 25. 用事件模式实现通知 事件是一种内建的委托,用来为事件处理函数提供类型安全的方法签名. 事件提供了一种标准的机制来通知侦听者.事件是实现广播类型行为信息的标准方式. 26. 避免返回对内部类对象的引用 共有4种不同的策略可以防止类型的内部数据结构遭受有意或无意的修改: 值类型,常量类型,接口和包装器(wrapper). 27. 让类型支持序列化 持久化(persistence)是类型的一个重要特性.只要类型表示的不是UI控件,窗体或者表单,支持序列化都是非常有必要的. .NET的序列化支持非常简单,没有任何理由不提供.大多数情况下,只要添加一个Serializable特性就足够了. [Serializable] // 直接将类序列化,但要求类中所有的类型都能被序列化 public class MyType { private string lable; // 支持序列化 private int value; // 支持序列化 // 只有 OtherClass 类也支持序列时,MyType类才不会报错 [NonSerialized] // 表示此类型不进行序列化 private OtherClass OtherThing; // 自定义类型作为成员时,就会看到尽可能地为类型添加序列化支持的意义 } Serializable 特性同时支持二进制序列化和SOAP序列化. 28. 提供粗粒度的因特网服务API API的粒度越细,所花费在等待数据返回上的额外时间也就越多. 当与远端计算机通信时,希望同时降低通信的频率以及每次通信时传递的数据量. 适当选择较少通信次数,并尽量一次传输更多的数据. 29. 支持泛型协变和逆变 类型变体(type variance),即所谓的协变(covariance)和逆变(contravariance),定义了在何种情况下,某个类型可以 代替另一个类型使用. 若不能将一个类型替换成另一个,那么这个类型就叫做不变量. 若某个返回的类型可以由其派生类型替换,那么这个类型就是支持协变的. 若某个参数类型可以由其基类替换,那么这个类型就是支持逆变的. 应记住在可能的情况下为泛型接口和委托添加上 in(协变) 和 out(逆变) 参数. 让集合支持协变也就意味着,若两个类型之间存在着继承关系,那么两个包含了这两种类型的集合也存在着类似的继承关系. 支持逆变的类型参数公可作为方法参数或在委托参数的某些位置中出现. 方法的参数支持逆变(in),方法的返回类型支持协变(out). 第四章 使用框架 30. 使用覆写而不是事件处理函数 .NET类提供了两种不同的方式,都可以用来处理系统中触发的事件---事件处理函数,覆写基类中的虚方法. 在派生类中,你只应该覆写虚方法.而事件处理函数则应该仅用在那些不相关对象的交互中. 覆写仅能用在派生类中,而其他所有类都必须使用事件机制. 31. 使用IComparable<T>和IComparer<T>实现顺序关系 当需要对一个集合中的类型进行排序和搜索时,这个类型就需要实现顺序关系. IComparable定义了类型的自然顺序,而IComparer则用来描述其他的顺序. IComparable接口包含了一个方法--ComparaTo().若是当前对象小于补比较对象,则返回值小于0,否则大于0. 若二者相等,将返回0. 32. 避免使用IConeable接口. 此接口通常只为那些支持复制的类型实现该接口,以便自然地完成复制操作. 一旦某个类型实现了oneable接口,那么其派生类也必须同样实现oneable接口. 33. 仅用new修饰符处理基类更新 使用new操作符修饰类成员可以重新定义继承自基类的非虚成员. 即指出某个方法是属于基类中同名方法的新版本. new修饰符只是用来解决升级基类所千万的基类方法和派生类方法冲突的问题. 34. 避免重载基类中定义的方法 35. PLINQ如何实现并行算法. 第一个分区方法: 范围分区 1000 分给4个分区,一个分区 250个 第二个分区方法: 区块分区. 另外两个分区算法用来对特定的查询操作进行优化:第一种是条带分区,第二种是散列分区 36. 理解PLINQ 在I/O 密集场景中的应用 37. 注意并行算法中的异常 第五章 C#中的动态编程 静态类型和动态类型各有所长.动态类型能够加速开发过程,更易于与不同系统进行交互. 静态类型能让编译器帮你找到很多错误,因为编译器能在编译时候进行检查,所以运行时检查即可省略,进而提高执行效率. 38. 理解动态类型的优劣. C#之所以提供动态类型,是为了架起使用其他系统的桥梁. public static dynamic Add(dynamic left,dynamic right) { return left + right; } dynamic answer =Add(5,5); answer = Add(5.5,3.9); dynamic label = Add("ABC" ,"DEF"); dynamic tomorrow = Add(DateTime.Now,TimeSpan.FromDays(1)); 以上都可运行. 38. 使用动态类型表达泛型类型参数的运行时类型 System.Linq.Enumerable.Cast<T>将序列中的每个对象都转换成了类型T. 40. 将接受匿名类型的参数声明为 dynamic. 匿名类型的一个不足之处是,它不太容易作为方法的参数和返回值使用.因为匿名对象的类型是由编译器生成的. 41. 用DynamicObject或IDynamicMetaObjectProvider实现数据驱动的动态类型. 创建带有动态功能的类型的最简单方法就是继承System.Dynamic.DynamicObject. 42. 如何使用表达式API .NET提供了一系列能在运行时反射类型或创建代码的API. 第一个场景解决了通信框架中的常见问题.使用WCF/远程调用或WEB服务的传统流程是,使用某种代码生成工具为来特定的服务生成客户端的代理. 43. 使用表达式将延迟绑定转换为预先绑定 延迟绑定API要使用符号(symbol)信息来实现.而预先编译好的API则无需这些信息,因为编译器已经解释了符号引用. 44. 尽量减少在公有API中使用动态对象 在静态类型的系统中,动态对象仍有一些"水土不服".虽然类型系统会将动态对象看做System.Object,但实际上却是 System.Object的一个特例. 若你要在程序中使用动态特性,请尽量不要在公有接口中使用,这样即可将动态类型限制在一个单独的对象(或类型)中, 避免动态类型脱离控制,影响到程序的其它部分或其他使用你的程序的代码中. 第六章 杂项 45. 尽量减少装箱和拆箱 值类型是数据的容器,并不支持多态. 装箱将把一个值类型放在一个未确定类型的引用对象中,让这个值类型也能在需要引用类型的地方使用. 拆箱则是指从箱中获取出其中值类型的副本.在需要System.Object类型的时候,装箱和拆箱允许你在此处使用 值类型.装箱和拆箱都是较为影响性能的操作,有时清空会创建对象的临时副本,进而导致一些难以发现的BUG. 小心到System.Object的隐式转换,若是可以避免的话,不要用值类型代替System.Object. 46. 为应用程序创建专门的异常类 异常是一种报告错误的机制,在这种机制中,错误的抛出位置和处理位置可能会相距甚远. 因此,有关错误的所有信息都必须包含在异常对象中. 第一步是了解何时以及为何创建新的异常类,并考虑应该如何组织信息完备的异常层次结构. 47. 使用强异常安全保证 抛出异常时,也就是在应用程序中引入一个中断事件.这会影响到程序的控制流程,导致期望的行为不能发生. 三种异常来保证程序:基础保证,强保证,以及无抛出保证. 基础保证状态是指没有资源泄漏,而且所有的对象在你的应用程序抛出异常后(即在任何一个finally子句结束之后) 是可用的. 强异常保证是创建在基本保证之上的,而且添加了一个条件,就是在异常抛出后,程序的状态不发生改变. 无抛出保证则表示操作绝对不失败,也就是从在某个操作后绝不会发生异常. 强异常保证是在异常恢复和简化异常处理之间最平衡的一种做法. .NET CLR 中提供了一些基本保证功能.运行环境处理托管内存.只有在一种情况下可能会有资源泄漏,那就是在异常抛出后, 你的程序还使用着一个实现了IDisposable接口的资源. 强异常保证是指,如果一个操作因为某个异常中断,程序将维持原状态不改变. 操作要么彻底完成,要么就不会修改程序的任何状态. 无抛出异常:听起来很完美,但这种想法是不实际的.不过在某些小的范围内,方法必须要做到无抛出保证. 终结器和销毁(dispose)方法以及委托对象所绑定的目标方法就必须保证无异常抛出. 48. 尽量使用安全的代码. 一般来说,你所创建的C#代码几乎都是安全的,除非你的c#编译器上打开了不安全的编译开关/unsafe, 否则你所创建的都是可验证的安全代码./unsafe允许用户使用指针,而指针是CLR无法验证的. 对于内存访问的建议很简单:应该尽可能避免访问非托管内存,若确实有这方面的需要,那么应将其隔离在独立的程序集中. 49. 实现与CLS兼容的程序集. .NET运行环境是与语言无关的,开发者可以用不同的.NET语言编写组件,而且在实际开发中往往就是这样. 你创建的程序集必须与CLS保持兼容,这样才能保证其他的开发人员可以用另一种语言来调来你的组件. 若想创建CLS兼容的程序集,必须遵从两个规则.首先,所有参数以及从公有的和受保护的成员上返回的值都必须是与CLS兼容的. 添加特性: [assembly:System.CLSCompliant(true)] CLS兼容规范只对那些将把内容暴露到外部的接口生效. 其次,其他与CLS不能兼容的公有或者受保护成员必须提供一个CLS兼容的版本(即能够实现同样的功能). 下面这些构造中涉及的任何类型都必须是CLS兼容的: 基类 公有或者受保护的方法和属性的返回值. 共有及受保护的方法和索引器的参数. 运行时事件参数. 公有接口,包括声明和实现. 50. 实现小尺寸,高内聚的程序集. 将程序拆分成多个程序集,把相差的类型放在同一个程序集中.
先保留下来.