Effective C# 要点小结,不懂也得写
Item 1: Use Properties Instead of Accessible Data Members
数据绑定支持属性不支持public数据成员
属性的方法实现中,对get和set访问器使用锁提供数据同步访问
Item 2: Prefer readonly to const
readonly运行时常量;const编译时常量,只能用于基元类型、枚举、字符串
const在编译时将替换成具体的常量,引用时若未编译所有程序集,const常量值不会更新,readonly常量值更新。
const性能较高但灵活性相对低
Item 3: Prefer the is or as Operators to Casts
用户自定义类型转换只针对编译时期类型,运行时期对用户自定义转换不会执行
as进行类型转换会返回null,不能用于值类型(值类型不可能为null)
不支持as转换时,先使用is进行异常去除或转换,再用as进行转换
类型约束、强制类型转换
Item 4: Use Conditional Attributes Instead of #if
条件特性只能运用于方法,条件特性方法返回值必是void,没有参数
多个条件特性逗号连接表示OR操作,使用预编译块可设置条件特性AND操作
#if ( VAR1 && VAR2 )
#define BOTH
#endif
Item 5: Always Provide ToString()
友好信息输出
System.IFormattable.ToString()格式化字符串输出接口 一些格式必要实现
添加IFormatProvider 和ICustomFormatter接口定制消息输出
Item 6: Understand the Relationships Among the Many Different Concepts of Equality
ReferenceEquals()判断引用相等
静态Equals()先引用判断再值判断
引用类型的判断时,使用值语义时使用重写Equals()方法
创建值类型时重写operator==()
Item 7: Understand the Pitfalls of GetHashCode()
GetHashCode()仅应用在基于散列的集合定义键的散列值,如HashTable< T >或Dictionary< K,V >
重写GetHashCode()条件:相等对象返回相同哈希值;对象的GetHashCode()必是实例不变量;针对所有输入产生整数随机分布
Item 8: Prefer Query Syntax to Loops
查询语法相比循环结构能创建多种组合的API
使用.AsParallel()能够并行执行查询
Item 9: Avoid Conversion Operators in Your APIs
隐式转换操作 当一个类型转换到另一个类型
显式转换 当代码中强制转换
转换会再编译时丢失对象
当替代对象可用,即为临时对象或能访问内部域
Item 10: Use Optional Parameters to Minimize Method Overloads
使用默认参数和命令参数可以创造使用者想要的重载组合
创建新版本时必须创建额外参数的重载
避免更改参数名称
Item 11: Understand the Attraction of Small Functions
小函数可让JIT编译器很容易支持寄存器化–处理器选择寄存器而非栈存储局部变量
小函数更可能使用内联方式调用
Item 12: Prefer Member Initializers to Assignment Statements
初始化语句在基类构造函数之前执行
变量声明时初始化避免多个构造函数和成员变量难以保持同步
不应使用初始化语句的情况:值类型对象初始化为0或引用类型初始化为null;变量在所有构造函数中初始化是不同的;构造函数中初始化便于异常处理
Item 13: Use Proper Initialization for Static Class Members
静态构造器会在一个类的任何方法、变量或者属性访问之前执行
静态字段同样会在静态构造器之前运行,同时静态构造器有利于异常处理。
Item 14: Minimize Duplicate Initialization Logic
构造函数链即一个构造函数调用另一个构造函数
构造函数使用默认参数有利于减少构造函数的重载
对象变量初始化的过程:static变量默认存0、static变量初始化执行、基类静态构造函数执行、静态构造函数执行、实例成员变量默认存0、实例成员变量初始化、恰当的基类实例构造函数执行、实例构造函数执行
Item 15: Utilize using and try/finally for Resource Cleanup
非托管资源类型必须使用 IDisposable 接口的 Dispose() 方法释放
using块能确保可回收对象被恰当回收(保证了Dispose的执行),一个using块相当于一个try/finally块
并非所有对象实现了IDisposable,可使用as进行转换
在IDisposable接口的Dispose()方法中用GC.SuppressFinalize()可通知垃圾收集器不再执行析构函数
Item 16: Avoid Creating Unnecessary Objects
引用类型的局部变量使用频繁,应该将其提升为成员变量,或者,使用单例模式让一个类提供常用的实例
StringBuilder可变字符串类,进行复杂的字符串操作
Item 17: Implement the Standard Dispose Pattern
非内存资源使用时必有终结器,GC完成没有终结器的内存对象后,实现终结器对象的添加,启动新的线程执行终结器
实现IDisposable.Dispose()主要任务:释放所有的非托管资源;释放所有的托管资源;设置一个状态标记来表示是否已经执行了Dispose();调用GC.SuppressFinalize(this)取消对象的终结操作
为需要多态的类型添加一个受保护的虚方法Dispose(),派生类通过重写这个方法来释放自己的任务
Item 18: Distinguish Between Value Types and Reference Types
值类型不支持多态,适合存储应用程序操作的数据,而引用则支持多态,适用于定义应用程序的行为
值类型具有较少的堆内存碎片、内存垃圾和间接访问时间,其在方法中的返回是以复制的方式进行,避免暴露内部结构到外界
值类型应用场景:数据存储、公共接口完全由一些数据成员存取属性定义、没有子类、没有多态性为
Item 19: Ensure That 0 Is a Valid State for Value Types
值类型的默认为0
枚举类型的0不应为无效的状态;在FlagsAttribute是应确保0值为有效的状态
在字符串为空时可以返回一个string.Empty的空字符串
Item 20: Prefer Immutable Atomic Value Types
不可变类型修改状态需要创建新的对象而非修改已存在的实例
变量初始化三种策略:构造器、工厂方法、构造可变辅助类(如StringBuilder)
Item 21: Limit Visibility of Your Types
public访问限制尽可能少的使用
public区域越少有助于覆盖率单元测试
public类越少,扩展和修改实现的选择越多
Item 22: Prefer Defining and Implementing Interfaces to Inheritance
接口描述对象的表现方式,不提供成员变量,无实现;基类描述对象是什么,提供子类一些实现描述共有行为
接口比较稳定,将功能封装在一个接口中;基类可进行扩展
Item 23: Understand How Interface Methods Differ from Virtual Methods
接口声明的成员是非虚的
虚函数适用于继承关系的类对象
内联函数、构造函数、静态成员函数不能为虚函数,析构函数可以为虚函数
在基类中实现一个接口时,派生类需要使用new来隐藏对基类方法的使用
Item 24: Express Callbacks with Delegates
委托对象本身不提供任何异常捕获,所以任何的多播委托调用都会结束整个调用链
通过显示调用委托链上的每个委托目标可以避免多播委托仅返回最后一个委托的输出。
委托提供了运行时利用回调的最好方式,可在运行时确定委托目标
Item 25: Implement the Event Pattern for Notifications
.Net事件模式就是观察者模式的语法规范
事件是构建在委托之上提供类型安全函数签名的处理
{
delegate: 就是原始的委托,可以理解为方法指针或方法的签名
Action: 是没有返回值的泛型委托
Func:有返回值的泛型委托
Predicate<>:返回值为 bool 类型的谓词泛型委托
Effective C# 改善C#程序的50种方法
event:对 delegate 的封装
}
Item 26: Avoid Returning References to Internal Class Objects
四种策略保证内部数据结构不被任意修改:值类型、不可变类型、接口、包装器
Item 27: Prefer Making Your Types Serializable
.NET序列化会将对象的所有成员保存到输出流中
使用序列化特性[Serializable],不可序列化成员默认初始化值0或null,如果默认不正确,需实现IDeserialization接口进行初始化
Item 28: Create Large-Grain Internet Service APIs
服务器和客户端高效通信的两个权衡点:数据量和频率–一次数据请求的完整性和网络宽带负载
Item 29: Support Generic Covariance and Contravariance
类型可变性:协变(使用一般类型的地方可以传入特殊类型的对象)和逆变(使用特殊类型的地方可以传入一般类型的对象)
泛型和委托
in逆变 out协变 函数参数修饰接口
Item 30: Prefer Overrides to Event Handlers
系统事件处理方式:事件处理器、重载基类的虚函数
一个事件处理器抛出异常,则事件链上的其他处理器将不会被调用,而重载的虚方法则不会出现这种情况;
重载要比关联事件处理器高效得多,事件处理器需要迭代整个请求列表,这样占用了更多的CPU时间;
事件在运行时响应,能够挂载多个事件处理器
当基类有一个函数处理一个事件时,重载方式优先
Item 31: Implement Ordering Relations with IComparable and IComparer
IComparable接口用于为类型实现最自然的排序关系,重载四个比较操作符,可以提供一个重载版的CompareTo()方法,让其接受具体类型作为参数;
IComparer用于提供有别于IComparable的排序关系,或者为我们提供类型本身说没有实现的排序关系。
Item 32: Avoid ICloneable
对于可能需要支持ICloneable接口的基类,应该为其创造一个受保护的复制构造器,并应当避免支持IConeable接口
不管是值类型还是引用类型如果实现了 ICloneable 接口,这个类的成员变量和继承结构也要实现 ICloneable ,才能做到深复制和浅复制的一致性。
Item 33: Use the new Modifier Only to React to Base Class Updates
尽量不用new修饰符,仅当基类更新时使用
Item 34: Avoid Overloading Methods Defined in Base Classes
重写基类的方法时,只有强制类型转换时才会调用基类的方法
重写解析规则是优先选择编译时继承结构最底端的子类的方法
Item 35: Learn How PLINQ Implements Parallel Algorithms
分区算法:范围分区、块分区、带分区、哈希分区
另三个并行任务的算法:Pipelining、Stop&Go、Inverted Enumeration
Item 36: Understand How to Use PLINQ for I/O Bound Operations
PLINQ 使用固定的线程数量
Parallel Task 库和 PLINQ 比之前的库提供更好的语言层次对异步编程的支持
Item 37: Construct Parallel Algorithms with Exceptions in Mind
如果出现异常你的并行算法就必须支持回滚
Parallel Task 库使用
AggregateExceptoion 类持有并行算法排除的所有异常,必须处理AggregateException 以控制线程初始化所有后台工作
Item 38: Understand the Pros and Cons of Dynamic
表达式更适合简单的计算
构建基于已存在类型的特定成员的方法的最好的方式是编写动态方法并且在运行时推断具体的选择。动态实现要找到一个恰当的实现,使用并缓存有助于更好的性能。它比单纯的静态类型解决方法更耗时,却比表达式树的解析更简单。
Item 39: Use Dynamic to Leverage the Runtime Type of Generic Type Parameters
//编写一个泛型方法并利用运行时形象进行类型转换
//Convert 与 Cast 的比较中,ConverT 适用于更多的类型转换的情况
//泛型方法不知道类型参数代表的类型的特定的功能,动态可使运行时反射使问题得到解决
使用泛型参数类型对动态对象进行强制类型转换。
Item 40: Use Dynamic for Parameters That Receive Anonymous Types
匿名参数的一个缺陷是你不能轻易让一个方法使用其作为参数或返回值
动态调用意味着需要支付额外的开销
当需要一到两个使用匿名类型的实用方法,动态调用是创建这个行为的简单的方法
Item 41: Use DynamicObject or IDynamicMetaObjectProvider for Data-Driven Dynamic Types
System.Dynamic.DynamicObject或System.Dynamic.IDynamicMetaObjectProvider创建自定义类型实现动态功能
动态类型的创建首要采用System.Dynamic.DynamicObject,当必须使用不同的基类,实现IDynamicMetaObjectProvider
Item 42: Understand How to Make Use of the Expression API
使用表达式和表达式树 比使用反射类更优
lambda可以被编译成委托
表达式API应用的两种方法:创建参数为表达式的方法;在运行时创建代码
Item 43: Use Expressions to Transform Late Binding into Early Binding
实现INotifyPropertyChanged和INotifyPropertyChanging消除对属性名的依赖
Item 44: Minimize Dynamic Objects in Public APIs
动态参数的方法返回值也是动态的,需要限制动态执行范围,返回静态类型
当代码依赖于在另一个环境中创建的动态类型,将其封装动态对象,并使用不同的静态类型提供公共接口
Item 45: Minimize Boxing and Unboxing
装箱把值类型转换成引用类型
使用泛型类和泛型方法能避免装拆箱操作
注意一个类型到System.Object的隐式转换,同时值类型不应该被替换为System.Object类型
Item 46: Create Complete Application-Specific Exception Classes
可能有不同的修复行为时,我们才应该创建多种不同的异常类,通过提供异常基类所支持的所有构造器,可以为应用程序创建功能完整的异常类,使用InnerException属性可以保存更低级别错误条件所产生的所有错误信息
Item 47: Prefer the Strong Exception Guarantee
强异常保证在从异常中恢复和简化异常处理之间提供了最好的平衡,在操作因为异常而中断,程序的状态保留不变
终结器、Dispose()方法和委托对象所绑定的目标方法在任何情况下都应当确保他们不会抛出异常
Item 48: Prefer Safe Code
常见受保护的资源为非托管资源和文件系统,其他受保护的资源包括数据库、网络端口、Windows注册表和打印子系统
尽可能的避免访问非托管内存,隔离存储不能防止来自托管代码和受信用户的访问;
程序集在Web上运行时可以考虑使用隔离存储,当某些算法确实需要更高的安全许可时,应该将那些代码隔离在一个单独的程序集中
Item 49: Prefer CLS-Compliant Assemblies
要创建兼容cls的总成,您必须遵循两条规则。首先,来自公共和受保护成员的所有参数和返回值的类型必须CLS兼容的。第二,任何不遵守规则的公众或受保护的成员必须有一个符合cls的同义词。
Item 50: Prefer Smaller, Cohesive Assemblies
为特定功能创建小的聚集的组件,为常用功能创建大的宽泛的组件