第7章 使用规范
7.1. 数组
@H_502_35@ü 要在公用API中优先使用集合,而不是优先使用数组。@H_404_51@
@H_404_51@
@H_502_35@û 不要使用只读的数组字段。@H_404_51@
public static readonly char[] chars = { '<','>','=' };//bad@H_404_51@
@H_404_51@
@H_502_35@ü 考虑使用不规则数组,而不要使用多维数组。@H_404_51@
//good@H_404_51@
int[][] jaggedArray={@H_404_51@
new int[]{1,2,3},@H_404_51@
new int[]{1,3,4,5},@H_404_51@
new int[]{8}@H_404_51@
};@H_404_51@
@H_404_51@
//bad@H_404_51@
int[,] multiDimArray={@H_404_51@
{1,4},@H_404_51@
{2,@H_404_51@
{4,0}@H_404_51@
};@H_404_51@
@H_404_51@
7.2. Attribute
@H_502_35@ü 要在命名自定义attribute类时添加“Attribute”后缀。@H_404_51@
@H_404_51@
@H_502_35@ü 要在定义自己的attribute时使用AttributeUsageAttribute。@H_404_51@
@H_404_51@
@H_502_35@ü 要为可选参数提供可设置的属性。@H_404_51@
@H_404_51@
@H_502_35@ü 要为必填参数提供只读属性。@H_404_51@
@H_404_51@
@H_502_35@ü 要提供构造函数参数来对鼻涕 参数进行初始化。@H_404_51@
@H_404_51@
@H_502_35@û 避免提供构造函数参数来对与可选参数对应的属性进行初始化。@H_404_51@
@H_404_51@
@H_502_35@û 避免对自定义attribute的构造函数进行重载。@H_404_51@
@H_404_51@
@H_502_35@ü 要尽可能将自定义attribute类密封起来。这样会使查找attribute更快。@H_404_51@
[AttributeUsage(@H_404_51@
AttributeTargets.Class | AttributeTargets.Method,@H_404_51@
AllowMultiple = true,Inherited = false)]@H_404_51@
public class LastModifiedAttribute : Attribute@H_404_51@
{@H_404_51@
private readonly DateTime dateModified;@H_404_51@
private readonly string changes;@H_404_51@
private string issues;@H_404_51@
@H_404_51@
public LastModifiedAttribute(string dateModified,string changes)@H_404_51@
{@H_404_51@
this.dateModified = DateTime.Parse(dateModified);@H_404_51@
this.changes = changes;@H_404_51@
}@H_404_51@
@H_404_51@
public DateTime DateModified@H_404_51@
{@H_404_51@
get { return dateModified; }@H_404_51@
}@H_404_51@
public string Changes@H_404_51@
{@H_404_51@
get { return changes; }@H_404_51@
}@H_404_51@
public string Issues@H_404_51@
{@H_404_51@
get { return issues; }@H_404_51@
set { issues = value; }@H_404_51@
}@H_404_51@
}@H_404_51@
7.3. 集合
@H_502_35@û 不要在公用API中使用弱类型集合。@H_404_51@
@H_404_51@
@H_502_35@û 不要在公用API中使用ArrayList或List<T>。当客户代码修改时无法收到消息。如Add()方法@H_404_51@
@H_404_51@
@H_502_35@û 不要在公用API中使用Hashtable或Dictionary<TKey,TValue>。@H_404_51@
@H_404_51@
@H_502_35@û 不要使用IEnumerator<T>、IEnumerator或实现了这两个接口之一的任何其他类型,除非是作为GetEnumerator方法的返回值。@H_404_51@
@H_404_51@
@H_502_35@û 不要在同一个类型中同时实现IEnumerator<T>和IEnumerable<T>。对非泛型接口IEnumerator和Enumerable来说也同样如此。类型要么是集合,要么是枚举器,不能两者都是。@H_404_51@
7.3.1. 集合参数
@H_502_35@ü 要用最泛的类型来作为参数类型。大多数的集合为参数的成员都使用IEnumerable<T>接口。@H_404_51@
public void PrintNames(IEnumerable<string> names)@H_404_51@
{@H_404_51@
foreach(string name in names)@H_404_51@
Console.WriteLine("name:{0}",name);@H_404_51@
}@H_404_51@
@H_404_51@
@H_502_35@û 避免使用ICollection<T>或ICollecton来做参数。@H_404_51@
ICollection<T> : IEnumerable<T>,IEnumerable@H_404_51@
ICollection : IEnumerable@H_404_51@
7.3.2. 集合属性与返回值
@H_502_35@û 不要提供可设置的集合属性。@H_404_51@
public Collection<Persons> Persons{get{...},set{...};}//bad@H_404_51@
@H_404_51@
@H_502_35@ü 要用Collection<T>或其子类作为属性或返回值来表示可读写的集合。@H_404_51@
public Collection<string> Legs@H_404_51@
{ @H_404_51@
get;@H_404_51@
}@H_404_51@
@H_404_51@
@H_502_35@ü 要用ReadOnlyCollection<T>或其子类作为属性或返回值来表示只读的集合。@H_404_51@
public ReadOnlyCollection<string> Eyes@H_404_51@
{@H_404_51@
get{return new ReadOnlyCollection<string>(eyes);}@H_404_51@
}@H_404_51@
@H_404_51@
@H_502_35@ü 考虑使用泛型集合基类的子类,而不要直接使用该集合。@H_404_51@
public class CowCollection:Collection<Cow>{...}@H_404_51@
@H_404_51@
@H_502_35@ü 要用Collection<T>或ReadOnlyCollecton<T>的子类来作为常用方法和常用属性的返回值。@H_404_51@
@H_404_51@
@H_502_35@ü 如果集合中存储的元素都有独一无二的键值,考虑使用有键集合(keyed collection),。@H_404_51@
@H_404_51@
@H_502_35@û 不要从集合属性或以集合为返回值的方法中返回null,替代方法是返回一个空集合或空数组。@H_404_51@
@H_404_51@
@H_502_35@û 不要让属性返回“snap collection”,属性应该返回“live collection”。表示某个时间点的状态的集合称为“snapshot Collection”,如数据库的查询。始终表示当前状态的集合称为live collection。@H_404_51@
@H_404_51@
@H_502_35@ü 要用snapshot collection或live IEnumerable<T>来表示不稳定的集合。@H_404_51@
7.3.3. 自定义集合的实现
@H_502_35@û 不要继承自非泛型的集合基类,比如CollectionBase,要使用Collection<T>。实际上,很多情况下,我们都不会从CollectionBase中派生类。知道内部的工作原理是好事,因为List<T>以相同的方式工作,但CollectionBase是向后兼容的。使用CollectionBase的唯一场合是要更多的控制向类的的用户展示的成员。如果希望集合类的Add()方法使用内部访问修饰符,则使用CollectionBase是最佳选择。@H_404_51@
@H_404_51@
@H_502_35@ü 要为强类型的非泛型集合实现IEnumerable<T>。如果合理,还可以考虑实现ICollection<T>甚至IList<T>。@H_404_51@
@H_404_51@
@H_502_35@û 如果类型的API很复杂,而且与集合的概念无关,避免为类型实现集合接口。@H_404_51@
@H_404_51@
@H_502_35@ü 要在为实现了IEnumerable的类型命名是添加“Collection”后缀。@H_404_51@
@H_404_51@
@H_502_35@ü 要在为实现了IDictionary或IDictionary<TKey,TValue>的类型命名是添加“Dictionary”后缀。@H_404_51@
@H_404_51@
@H_502_35@û 避免给集合抽象的名字添加代表其具体实现的后缀,比如“LinkedList”或“Hashtable”。@H_404_51@
@H_404_51@
@H_502_35@ü 考虑用集合元素的类型名作为集合名字的前缀。@H_404_51@
@H_404_51@
@H_502_35@ü 考虑给只读集合的名字添加“ReadOnly”前缀。@H_404_51@
7.3.4. 数组与集合之间的选择
@H_502_35@ü 要优先使用集合,而不是优先使用数组。@H_404_51@
@H_404_51@
@H_502_35@ü 考虑在底层API中使用数组,以降低内存的消耗并提高性能。@H_404_51@
@H_404_51@
@H_502_35@ü 要使用字节数组,而不要使用字节集合。@H_404_51@
public Collection<byte> ReadBytes(){...}//bad@H_404_51@
public byte[] ReadBytes(){...}//good@H_404_51@
@H_404_51@
@H_502_35@û 不要将数组用于属性。 @H_404_51@
7.4. ICloneable
@H_502_35@û 不要实现ICloneable。@H_404_51@
@H_404_51@
@H_502_35@û 不要在公用API中使用ICloneable.@H_404_51@
@H_404_51@
@H_502_35@ü 考虑为需要克隆机制的类型定义Clone方法。一定要在文档中明确说明该方法是深复制还是浅复制。@H_404_51@
7.5. Icomparable<T>与IEquatable<T>
@H_502_35@ü 要为值类型实现IEquatable<T>。主要从性能考虑,object.Equals会导致装箱操作。@H_404_51@
@H_404_51@
@H_502_35@ü 要在实现IEquatable<T>.Equals时,同样遵循为覆盖Object.Equals而制定的规范。@H_404_51@
@H_404_51@
@H_502_35@ü 要在实现IEquatable<T>的同时覆盖Object.Equals。@H_404_51@
@H_404_51@
@H_502_35@ü 考虑在实现IEquatable<T>的同时重载“operator ==” 和“Operator !=”。@H_404_51@
@H_404_51@
@H_502_35@ü 要在实现IComparable<T>的同时实现IEquatable<T>。@H_404_51@
@H_404_51@
@H_502_35@ü 考虑在实现IComparable<T>的同时重载比较操作符(<、>、<=、>=)。@H_404_51@
7.6. IDisposable
实现IDisposable就是实现Dispose模式。@H_404_51@
Dispose模式:@H_404_51@
public class Base : IDisposable@H_404_51@
{@H_404_51@
public void Dispose()@H_404_51@
{@H_404_51@
this.Dispose(true);@H_404_51@
GC.SupressFinalize(this);@H_404_51@
}@H_404_51@
@H_404_51@
protected virtual void Dispose(bool disposing)@H_404_51@
{@H_404_51@
if (disposing)@H_404_51@
{@H_404_51@
// 托管类@H_404_51@
}@H_404_51@
// 非托管资源释放@H_404_51@
}@H_404_51@
@H_404_51@
~Base()@H_404_51@
{@H_404_51@
this.Dispose(false);@H_404_51@
}@H_404_51@
}@H_404_51@
@H_404_51@
public class Derive : Base@H_404_51@
{@H_404_51@
protected override void Dispose(bool disposing)@H_404_51@
{@H_404_51@
if (disposing)@H_404_51@
{@H_404_51@
// 托管类@H_404_51@
}@H_404_51@
// 非托管资源释放@H_404_51@
base.Dispose(disposing);@H_404_51@
}@H_404_51@
}@H_404_51@
7.7. 对象
7.7.1. Object.Equals
@H_502_35@ü 要在覆盖Object.Equals方法时,遵守它定义的协定。@H_404_51@
x.Equals(x) 返回true。@H_404_51@
x.Equals(y)的返回值与y.Equals(x)相同。@H_404_51@
如果(x.Equals(y)&&y.Equals(z))返回true,那么x.Equals(z)也应该返回true。@H_404_51@
只要对象x和对象y未被修改,那么连续调用x.Equals(y)应该返回相同的值。@H_404_51@
x.Equals(null)应该返回false。@H_404_51@
@H_404_51@
@H_502_35@ü 要在覆盖Equals方法的同时,覆盖GetHashCode方法。@H_404_51@
@H_404_51@
@H_502_35@ü 考虑在覆盖Object.Equals方法的同时实现IEquatable<T>接口。@H_404_51@
@H_404_51@
@H_502_35@û 不要从Equals方法中抛出异常。@H_404_51@
@H_404_51@
@H_502_35@ü 要覆盖值类型的Equals方法。@H_404_51@
@H_404_51@
@H_502_35@ü 要通过实现IEquatable<T>来提供一个以值类型本身为参数的Equals重载方法。@H_404_51@
@H_404_51@
@H_502_35@ü 考虑对Equals进行覆盖以提供值相等语义,如果引用类型表示的是一个值。@H_404_51@
7.7.2. Object.GetHashCode
@H_502_35@ü 如果覆盖了Object.Equals方法,要覆盖GetHashCode方法。@H_404_51@
@H_404_51@
@H_502_35@ü 要确保对任何两个对象来说,如果Object.Equals方法返回true,那么它们的GetHashCode方法的返回值也应该相同。@H_404_51@
@H_404_51@
@H_502_35@ü 要尽力让类型的GetHashCode方法产生随机分布的散列码。@H_404_51@
@H_404_51@
@H_502_35@ü 要确保无论怎么更改对象,GetHashCode都会返回完全相同的值。@H_404_51@
@H_404_51@
@H_502_35@û 避免从GetHashCode方法中抛出异常。@H_404_51@
@H_404_51@
7.7.3. Object.ToString
@H_502_35@ü 要覆盖ToString方法,只有能返回既有用,又易于让人阅读的字符串。@H_404_51@
@H_404_51@
@H_502_35@ü 要尽量让ToString方法返回短小的字符串。@H_404_51@
@H_404_51@
@H_502_35@ü 考虑为每一个实例返回一个独一无二的字符串。@H_404_51@
@H_404_51@
@H_502_35@ü 要使用易于阅读的名字,而不要使用虽然独一无二,但却让人无法理解的ID。@H_404_51@
@H_404_51@
@H_502_35@ü 要在返回与culture有关的信息时,根据当前线程的culture对字符串进行格式化。@H_404_51@
@H_404_51@
@H_502_35@û 不要从ToString方法返回空字符串或null。@H_404_51@
@H_404_51@
@H_502_35@û 避免从ToString方法抛出异常。@H_404_51@
@H_404_51@
@H_502_35@ü 要确保ToString方法不会产生副作用。@H_404_51@
@H_404_51@
@H_502_35@ü 要通过ToString的覆盖方法来报告对安全敏感的信息,前提是必须先获得相应的许可。@H_404_51@
@H_404_51@
@H_502_35@ü 考虑让TosString方法输出的字符串能够为该类型的解析方法正确地解析。@H_404_51@
DateTime now = DateTime.Now;@H_404_51@
DateTime parsed = DateTime.Parse(now.ToString());@H_404_51@
7.8. Uri
@H_502_35@ü 要使用System.Uri来表示URI和URL数据。@H_404_51@
@H_404_51@
@H_502_35@ü 考虑为最常用的带System.Uri参数的成员提供基于字符串的重载成员。@H_404_51@
@H_404_51@
@H_502_35@û 不要不假思索地为所有基于System.Uri的成员提供基于字符串的重载成员。@H_404_51@
@H_404_51@
@H_502_35@ü 要调用基于System.Uri的重载成员,如果有的话。@H_404_51@
@H_404_51@
@H_502_35@û 不要在字符串中存储URI/URL数据。@H_404_51@
7.9. System.Xml的使用
@H_502_35@û 不要用XmlNode或XmlDocument来表示XML数据。要用IXPathNavigable来代替。基于IXPathNavigable的API不仅消耗的内存较少,而且可以解除API与某个具体的XML数据源之间的耦合。@H_404_51@
@H_404_51@
@H_502_35@ü 要用XmlReader作为方法的XML数据输入,或用IXPathNavigable作为方法返回的XML。@H_404_51@
@H_404_51@
@H_502_35@ü 要为类型实现IXPathNavigable接口,如果该类型表示下层对象模型或数据源的XML视图。@H_404_51@
@H_404_51@
@H_502_35@û 不要从XmlDocument派生子类,如果想要创建的类型表示下层对象模型或数据源的XML视图。@H_404_51@
7.10. 相等性操作符
@H_502_35@û 不要只重载相等性操作符中的一个。@H_404_51@
@H_404_51@
@H_502_35@ü 要确保Object.Equals与相等性操作符具有完全相同的语义及相近的性能。@H_404_51@
@H_404_51@
@H_502_35@û 避免从相等性操作符中抛出异常。@H_404_51@
7.10.1. 值类型相等性操作符
@H_502_35@ü 要重载值类型的相等性操作符,如果相等性是有意义的。@H_404_51@
7.10.2. 引用类型相等性操作符
@H_502_35@û 避免重载可变引用类型的相等性操作符。@H_404_51@
@H_404_51@
@H_502_35@ü 考虑不要重载引用类型的相等性操作符,即使覆盖了Equals或实现了IEquatable<T>。@H_404_51@
@H_404_51@