阅读笔记_《effective-csharp》

来源:互联网 发布:国产女装品牌知乎 编辑:程序博客网 时间:2024/04/30 19:23

记录一下 《effective-csharp》 这本书里觉得比较有用的东东

gitbook:https://wizardforcel.gitbooks.io/effective-csharp/content/


索引器(indexers )

  • 类似数组下标的方式 在类中使用

    class Abs {      public Dictionary<string, string> _map = new Dictionary<string, string>();      public string this[string key] {          get {              return _map.ContainsKey(key) ? _map[key] : null;          }          set {              _map.Add(key, value);          }      }  }      Abs a = new Abs();      a["aaa"] = "sdf";

属性访问器(getter setter)

  • 在写c++的时访问属性一般都会提供个 getter setter 方法,方法体不大的时候编译器会做 inline 优化,在csharp中有这样的 语法糖,它提供了 inline 优化

      class AAA {      int mA = 0;      public int A {          get { return mA; }          set { mA = value; }      }  }
  • 如果不想被 inline 优化,可以使用 System.Runtime.CompilerServices.MethodImpl 特性, 指定方法不被内联

    [MethodImpl(MethodImplOptions.NoInlining)]public int A(){}
  • 不会被 inline 优化的情况:虚函数 或者 包含 try/catch 的函数


偏爱 readonly 而不是 const

  • C# 有两种常量:编译时常量 const 和 运行时常量 readonly。

  • 区别:

    1. 编译时常量 会比 编译时常量 稍微快点, 但更不灵活
    2. 编译时常量 还可以在方法体中声明。运行时常量 不能在方法体重声明
    3. 编译时常量 和 运行时常量 访问方式不同导致不同的行为。 在目标代码中 编译时常量 会被替换成 常量值;运行时常量 的值是在运行时得到的。 当你引用一个只读(read-only) 常量, IL 会引用一个readonly 变量而不是直接使用值
    4. 编译时常量只能在基本类型(内建整数和浮点数类型) , 枚举类型, 或字符串。 编译时常量要求类能用有意义的常量赋值初始化。 而只有基本类型才能在 IL 代码中使用常量(literal values) 来替换。 不能使用使用 new 操作法初始化编译时常量 ,即使它是一个值类型 。
    5. const编译时 编译到程序集,如果别的程序集使用了这个变量,这个变量将不会有任何改变;而readonly是运行时get到值,所以readonly跟灵活
  • 示例:

    // Compile time constant:public const int Millennium = 2000;// Runtime constant:public static readonly int ThisYear = 2004;

选择 is 或 as 而不是强制类型转换

  • 使用as做类型转换,而不是强制装换(XXX)obj,如果obj没有继承关系,as编译时会报错,而(XXX)obj不会。

  • 不能使用as的情况:参数为 值类型,此时可以用is来判断。(is只用于as无法使用的情况,否则多余,如果知道参数是引用类型就用 as)

  • 示例

    object o = Factory.GetObject();MyType t = o as MyType;if (t != null) {//Do somethind about t.}

使用条件特性(conditional attribute) 代替 #if/#endif

  • 假如调试代码是 CheckState 函数里的

    [Conditional("DEBUG")]private void CheckState(){// same code as above}
  • 如果 DEBUG 变量被定义, 你的代码编译出来是这样的

      public string LastName {      get {          CheckState();          return lastName;      }  }
  • 如果 DEBUG 变量 没有 定义, 你的代码编译出来是这样的

      public string LastName {      get {          return lastName;      }  }
  • 如果使用 #if/#endif 块包住,也会产生一次 CheckState 函数的调用,即使 DEBUG 没有定义

    private void CheckState(){#if// same code as above#endif}
  • Conditional 只能使用在 void 返回值的函数中,否则编译错误。

  • 这种是 或 的表达式,如果要表达 与,拆分成两个方法,各用一个红

    [Conditional("DEBUG"), Conditional("DEBUG222")]private void CheckState() {}//等价于#if DEBUG || DEBUG222#endif

使用恰当的方式对静态成员进行初始化

  • 静态初始化语法 会比 静态构造函数 更早执行,静态初始化语法 和 静态构造函数 是最干净, 最清晰的方式去初始化静态成员变量

  • 实例第一次构造对象时的顺序

    1. static 变量默认存储为0。
    2. static 变量初始化执行。
    3. 基类静态构造函数执行。
    4. 静态构造函数执行。
    5. 实例成员变量默认存储为0。

    6. 实例成员变量初始化执行。

    7. 恰当的基类实例构造函数执行。
    8. 实例构造函数执行。

      • 后续同一类型的对象初始化从第5步开始因为类初始化只会执行一次。 而且, 步骤6和7会被优化构造函数初始化语法会引起编译器移除重复的指令。

使用构造函数链

  • 构造函数时 调用其他构造函数,减少各个构造函数中重复的初始化代码。类似重载函数的概念

      public class MyClass {// collection of data      private List<Circle> coll;      private string name;      public MyClass() : this(0, string.Empty) { }      public MyClass(int initialCount = 0, string name = "") {          coll = (initialCount > 0) ?  new List<Circle>(initialCount) : new List<Circle>();          this.name = name;      }  }

using 语法糖

  • using 语法糖会产生 try/finally 块

      SqlConnection myConnection = null;  // Example Using clause:  using (myConnection = new SqlConnection(connString))  {      myConnection.Open();  }  // 等价于  // example Try / Catch block:  try  {  myConnection = new SqlConnection(connString);  myConnection.Open();  } finally {      myConnection.Dispose();  }
  • 快速保护方法 是使用 as 语句可以转换为安全可回收对象不管是否实现 IDisposable 接口

    • using 语句对在编译时期类型实现 IDisposable 接口才能正常工作
    // Does not compile. 编译报错// Object does not support IDisposable.using (object obj = Factory.CreateResource()) {    Console.WriteLine(obj.ToString());}
    • 快速保护方法
    // The correct fix.// Object may or may not support IDisposable.object obj = Factory.CreateResource();using (obj as IDisposable) { //如果 obj 没有实现 IDisposable 接口,则这行代码 等价于 using(null) {    Console.WriteLine(obj.ToString());}

字符串拼接

  • 使用 sting.Format 或者 StringBuilder 替代 string 的 += 拼接操作
  • StringBuilder 类似 Vector 一样是动态扩容。

限制你的类型的可见性

  • 尽可能的减少类的可见性,例如某些类里需要一些数据结构类,应该设为 private 或 这 internal ,不让外部知道

让接口支持协变和逆变

  • 让接口支持 逆变、协变,限制 泛型只能是参数 或者 只能是返回值,例如Action 和 Func 这两个委托,这个有点抽象

      public interface IMyList2<out T> {      T GetElement();      //void ChangeT(T t); // 这里编译错误,因为 T 被 out 修饰,所以 T 只能做返回值,不能做参数  }  public class MyList2<T> : IMyList2<T> {      public T GetElement() {          return default(T);      }      public void ChangeT(T t) {          //Change T      }  }  //-------------------------------  public interface IMyList<in T> {      //T GetElement(); // 这里编译错误,因为 T 被 in 修饰,所以 T 只能做参数,不能做返回值      void ChangeT(T t);  }  //没有指定 in 或者 out,就可以即做 参数 又做 返回值  public class MyList<T> : IMyList<T> {      public T GetElement() {          return default(T);      }      public void ChangeT(T t) {          //Change T      }  }

上面这些是个人感觉比较能用到的东西