C#编码规范(修订)

  1. 避免将多个类放在一个文件里面。

  2. 一个文件应该只有一个命名空间,避免将多个命名空间放在同一个文件里面。

  3. 一个文件最好不要超过500行的代码(不包括IDE产生的代码)。

  4. 一个方法的代码长度最好不要超过25行。

  5. 避免方法中有超过5个参数的情况。如果超过了,则应使用 struct 来传递多个参数。

  6. 每行代码不要超过80个字符。

  7. 原则上,尽量不要手工的修改机器产生的代码。
    a) 如果需要编辑机器(IDE)产生的代码,编辑格式和风格要符合该编码标准。
    b) 尽可能地使用片断类来把被保持的部分分解为各个因素
    注:这里的翻译参考了灵感之源老兄的说法,在Visual c#2005中,C#的语法已经支持partial修饰符,它的作用是可以将一个完整的类分解各个部分类,在编译时,编译器会将它们构造为一个类。

    具体请参考
    http://blog.joycode.com/zhanbos/archive/2004/05/25/22402.aspx
    http://weblogs.asp.net/jaybaz_ms/archive/2004/04/28/122392.aspx

  8. 避免利用注释解释显而易见的代码。
    a) 代码应该可以自解释。好的代码本身就应具体良好的可读性,所使用的变量和方法命名一般情况下不需要注释。

  9. 文档应该仅用于assumptions, algorithm insights等等.

  10. 避免使用方法级的文档。
    a) 使用扩展的API文档进行说明。
    b) 只有在该方法需要被其他的开发者使用的时候才使用方法级的注释。(在C#中就是///

  11. 不要硬编码数字的值,总是使用构造函数设定其值。

  12. 只有是自然结构才能直接使用const(常量),比如一个星期的天数。

  13. 区别只读变量及常量的使用方法,如果想实现只读变量,可以直接使用readonly修饰符。

    public class MyClass
    {
        public readonly int Number;
        public MyClass(int someValue)
        {
            Number = someValue;
        }
        public  const int  DaysInWeek = 7;
    }
    
  14. 每个假设必须使用Assert检查
    a) 平均每15行要有一次检查(Assert)

    using System.Diagnostics;
    
    object GetObject()
    {}
    object obj = GetObject();
    Debug.Assert(obj != null);
    
  15. 代码的每一行都应该通过白盒方式的测试。

  16. 只抛出已经显示处理的异常。

  17. 在捕获(catch)语句的抛出异常子句中(throw),总是抛出原始异常,用以维护原始错误的堆栈分配。

    catch(Exception exception)
    {
        MessageBox.Show(exception.Message);
        throw ;  //和throw exception一样。
    }
    

    注:同理,不推荐在循环语句中,进行直接的return操作。

    for (int i=0;i<100;i++)
    {
        if (i==10)
        {
            return; //不推荐的方式
        }
    }
    
  18. 避免方法的返回值是错误代码。

  19. 尽量避免定义自定义异常类。

  20. 当需要定义自定义的异常时:
    a) 自定义异常要继承于ApplicationException。
    b) 提供自定义的序列化功能。

  21. 避免在单个程序集里使用多个Main方法。

  22. 只对外公布必要的操作,其他的则为internal。

  23. 避免使用友元程序集,因为它会增加程序集间的耦合度。

  24. 避免编写从指定的位置加载的程序集的代码。

  25. 使应用程序集尽量为最小化代码(EXE客户程序)。使用类库来替换包含的商务逻辑。

  26. 避免给枚举变量提供显式的值。

    //正确方法
    public enum Color
    {
        Red,Green,Blue
    }
    
    //避免
    public enum Color
    {
        Red = 1,Green =  2,Blue = 3
    }
    
  27. 避免指定特殊类型的枚举变量。

    //避免  
    public enum Color  : long
    {
        Red,Green,Blue
    }
    
  28. 即使if语句只有一句,也要将if语句的内容用大括号扩起来。

  29. 避免使用trinary条件操作符。

  30. 避免在条件语句中调用返回bool值的函数。可以使用局部变量并检查这些局部变量。

    bool IsEverythingOK()
    {}
    
    //避免
    if (IsEverythingOK ())
    {}
    
    //替换方案
    bool ok = IsEverythingOK();
    if (ok)
    {}
    
  31. 总是使用基于0开始的数组。

  32. 在循环中总是显式的初始化引用类型的数组。

    public class MyClass
    {}
    
    MyClass[] array = new  MyClass[100];
    for(int index = 0; index < array.Length;  index++)
    {
        array[index] = new  MyClass();
    }
    
  33. 尽量不要提供public 和 protected的成员变量,使用属性代替他们。

  34. 避免在继承中使用new而使用override来进行替换。

  35. 在不是sealed的类中总是将public 和 protected的方法标记成virtual的。

  36. 除非使用interop(COM+ 或其他的dll)代码否则不要使用不安全的代码(unsafe code)。

  37. 避免显式的转换,使用as操作符进行兼容类型的转换。

    Dog dog = new GermanShepherd();
    GermanShepherd shepherd = dog  as  GermanShepherd;
    if (shepherd != null )
    {}
    
  38. 当类成员包括委托的时候
    a) 在调用委托前,将它拷贝到一个本地变量中,用以避免并发争用条件。
    b) 在调用委托之前一定要检查它是否为null

    public class MySource
    {
        public event EventHandler  MyEvent;
        public void FireEvent()
        {
          //将委托拷到一个本地变量中。
            EventHandler temp = MyEvent;
            //确定它是否为空
            if(temp != null )
            {
                temp(this, EventArgs.Empty);
            }
        }
    }
    
  39. 不要提供公共的事件成员变量,使用事件访问器替换这些变量。

    public class MySource
    {
        MyDelegate m_SomeEvent;
    
        public event MyDelegate SomeEvent
        {
            add
            {
                m_SomeEvent += value;
            }
    
            remove
            {
                m_SomeEvent -= value;
            }
        }
    }
    
  40. 使用一个事件帮助类来公布事件的定义。

  41. 总是使用接口。

  42. 类和接口中的方法和属性至少为2:1的比例。

  43. 避免一个接口中只有一个成员。

  44. 尽量使每个接口中包含3-5个成员。

  45. 接口中的成员不应该超过20个。
    a) 实际情况可能限制为12个

  46. 避免接口成员中包含事件。

  47. 避免使用抽象方法而使用接口替换。

  48. 在类层次中显示接口。

  49. 推荐使用显式的接口实现。

  50. 从不假设一个类型兼容一个接口,并应防止查询那些接口。

    SomeType obj1;
    IMyInterface obj2;
    
    /* 假设已有代码初始化过obj1,接下来 */
    obj2 = obj1 as IMyInterface;
    if (obj2 != null)
    {
        obj2.Method1();
    }
    else
    {
        //处理错误
    }
    
  51. 表现给最终用户的字符串(一般指UI界面中的部分)不要使用直接编码,而应该要使用资源文件来替换。
    注:这样做的目的是方便软件的本地化。

  52. 不要直接编写可能会更改的基于配置的字符串,比如连接字符串。

  53. 当需要构建较长的字符串的时候,应该考虑使用StringBuilder不要使用string来处理。
    注:string每次要创建一个新的实例,较占用空间,并产生了相对StringBuilder更大的性能消耗。对于过于频繁的字符串操作,采用StringBuilder是一个良好的习惯。

  54. 避免在结构里面提供方法。
    a) 建议使用参数化构造函数
    b) 可以重载操作符

  55. 总是要给静态变量提供静态构造函数。

  56. 在能够使用早期绑定的情况下,尽量避免使用后期绑定。
    注:后期绑定虽然灵活,但带来的不仅仅是性能上的消耗,更多的是编码上的复杂性和混乱的逻辑。

  57. 使用应用程序的日志和跟踪。

  58. 除非在不完全的switch语句中否则不要使用goto语句。
    注:原则上不应使用goto语句,除非在能够大大减轻编码的复杂性,并不影响可读性的前提下才允许使用。

  59. 在switch语句中总是要有default子句来显示信息(Assert)。

    int number = SomeMethod();
    switch(number)
    {
        case 1:
            Trace.WriteLine("Case 1:");
            break;
        case 2:
            Trace.WriteLine("Case 2:");
            break;
        default :
            Debug.Assert(false);
            break;
    }
    
  60. 除非在构造函数中调用其他构造函数否则不要使用this指针。

    // 正确使用this的例子
    public class MyClass
    {
        public MyClass(string message)
        {}
    
        public MyClass()  : this("hello")
        {}
    }
    
  61. 除非你想重写子类中存在名称冲突的成员或者调用基类的构造函数否则不要使用base来访问基类的成员。

    // 正确使用base的例子
    public class Dog
    {
        public Dog(string name)
        {}
    
        virtual public void Bark(int howLong)
        {}
    }
    
    public class GermanShepherd : Dog
    {
        public GermanShe pherd(string name): base(name)
        {}
    
        override public void Bark(int howLong)
        {
            base.Bark(howLong);
        }
    }
    
  62. 基于模板的时候要实现Dispose()和Finalize()两个方法。

  63. 通常情况下避免有从System.Object转换来和由System.Object转换去的代码,而使用强制转换或者as操作符替换。

    class SomeClass
    {}
    
    //避免:
    class MyClass<T>
    {
    void SomeMethod(T t)
    {
            object temp = t;
            SomeClass obj = (SomeClass)temp;
    }
    }
    
    // 正确:
    class MyClass<T> where T : SomeClass
    {
        void SomeMethod(T t)
        {
            SomeClass obj = t;
        }
    }
    
  64. 在一般情况下不要定义有限制符的接口。接口的限制级别通常可以用强类型来替换之。

    public class Customer
    {}
    
    //避免:
    public interface IList<T> where T : Customer  
    {}
    
    //正确:
    public interface ICustomerList : IList<Customer>
    {}
    
  65. 不确定在接口内的具体方法的限制条件。

  66. 总是选择使用C#内置(一般的generics)的数据结构

  67. 初始化类的实例时,除非十分必要,否则不要赋null值。

  68. 使用后的实例,尽量不要将该实例的引用赋值和为nul,尤其是采用public来修饰的类成员l。
    1)如果该实例是临时引用,请使用using语句,然后在程序块中使用。
    2)如果需要释放资源,应可能地使用Dispose,采用null值的方法,该引用在指向下一个实例前,不会被回收。

Contributors: FHL