注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Code@Pig Home

喜欢背着一袋Code傻笑的Pig .. 忧美.欢笑.记忆.忘却 .之. 角落

 
 
 

日志

 
 

《Effective C#》读书笔记  

2012-02-29 21:54:47|  分类: lang_C# |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

C# Language Idioms


Item 1: Use Properties Instead of Accessible Data Members

class MyFoo

{

    public string Name { get; set; }     // property

    public int Age;                      // data member

}

property 可以当 method 一样用,可以 virtual。



Item 2: Prefer readonly to const

public const int val1 = 10;

public static readonly MyFoo val2 = new MyFoo();

const 相当于宏,是 compile time 替换的,只能用于 primitive type,比如:int, string

readonly 可以用于任何 class,是 runtime time const value



Item 3: Prefer the is or as Operators to Casts

用 as 来做类型转换

object o = Factory.GetObject();

MyType t = o as MyType;

if (t != null)

{

  // work with t

}

else

{

  // report the failure

}


object o = Factory.GetObject();

try

{

  MyType t = (MyType)o;

  // work with t

}

catch (InvalidCastException)

{

  // report the conversion failure

}


对于 primitive type 不能直接 as 的,可以先 is 判断

object o = Factory.GetValue();

int i = 0;

if (o is int)

  if = (int)o;



Item 4: Use Conditional Attributes Instead of #if

C# 支持传统的 #if / #endif

private void CheckState()

{…}


public static void Main()

{

#if DEBUG

  CheckState();

#endif

}


但是否启用某个函数,通过 [Conditional] 来设置,更佳。

Release版,编译器会把 CheckState() 编译到 assembly 中,但只消耗磁盘,无损效率。

函数返回值必须是 void。

[Conditional("DEBUG")]

private void CheckState()

{...}


public static void Main()

{

  CheckState();

}



Item 5: Always Provide ToString()

每个自己的类,都应该重载 System.Object.ToString() 函数,返回一个有意义的字符串。

class Foo

{

  public string Name { get; set; }

  public int Age { get; set; }

public Foo(string name, int age)

{

    Name = name;

    Age = age;

  }

 

  public override string ToString()

  {

    return String.Format("{0} - {1}({2})", typeof(Foo), Name, Age);

  }

}



Item 6: Understand the Relationships Among the Many Different Concepts of Equality

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);       // 自己有时候需要提供


函数一 static method Object.ReferenceEquals(),是否引用的是同一个对象。

int a = 7;

int b = 7;

Object.ReferenceEquals(a, b);     // false

Object.ReferenceEquals(a, a);     // true


函数二 static method Object.Equals(),如果引用的是不同对象,通过对象的 instance method Equals() 比较是否相等。

public static new bool Equals(object left, object right)

{

  if (Object.ReferenceEquals(left, right))

    return true;

  if (Object.ReferenceEquals(left, null) ||

      Object.ReferenceEquals(right, null))

    return false;

  return left.Equals(right);

}


函数三 instance method Equals(),

系统自带的 struct (Object.ValueType) 的 Equals() 实现比较低效,直接通过 reflection 对比所有字段的值。为自己的 struct 提供高效的 Equals()。

对于 class,看是否需要 value equal sematics,来决定是否提供 Equals()。

正确实现 instance Equals(),不能 throw exception。

class Foo : IEquatable<Foo>

{

  public string Name { get; set; }


  public Foo(string name) { Name = name; }

  public override bool Equals(object right)

  {

    if (object.ReferenceEquals(right, null))

      return false;

    if (object.ReferenceEquals(this, right))

      return true;

    if (this.GetType() != right.GetType())

      return false;

    return this.Equals(right as Foo);

  }

  #region IEqualtable<Foo> Members

  public bool Equals(Foo other)

  {

    return this.Name == other.Name;

  }

  #endregion

}

正确实现了 instance Equals,则可以通过 a.Equals(b) 来对比两者是否相当。


函数四 operator==

对于自定义的 value type,提供 operator==,效率原因。

对于自定义的 ref type,不提供 operator==,因为 .NET 使用 operator== 来比较两个对象的引用是否相等(reference semantics)。



Item 7: Understand the Pitfalls of GetHashCode()

GetHashCode() 是作为容器的 key 存在的,比如 HashSet<T>, Dictionary<T>。

GetHashCode() 应满足如下三条约定:

 1. 对于operator ==相等的object,其 hashcode 必须相等

 2. 对于 objA,任何时候调用 objA.GetHashCode() 返回值应该一致

 3. 生成的hashcode(int)应尽量平均分布,从而提高容器的效率

class/Object.GetHashCode() 保证如上的约定。

struct/ValueType.GetHashCode() 不保证如上的约定。

在充分理解了 GetHashCode() 的缺点后,再考虑是否需要给你定义的class/struct实现GetHashCode()



Item 8: Prefer Query Syntax to Loops

常用LINQ,开心又快乐。

using System; using System.Linq;

using System.Collections.Generic;


static class MyTest

{
  private static IEnumerable<Tuple<int,int>> produceIndices()
  {
    var storage = new List<Tuple<int, int>>();
    for (int x = 0; x < 100; x++)
      for (int y = 0; y < 100; y++)
        if (x + y < 100)
          storage.Add(Tuple.Create(x, y));

    storage.Sort((point1, point2) =>

(point2.Item1*point2.Item1 + point2.Item2*point2.Item2).CompareTo(

point1.Item1*point2.Item1 + point1.Item2*point1.Item2));

    return storage;

  }

  private static IEnumerable<Tuple<int,int>> queryIndices()

  {
    return from x in Enumerable.Range(0, 100)
           from y in Enumerable.Range(0, 100)
           where x + y < 100
           orderby (x*x + y*y) descending
           select Tuple.Create(x, y);
  }

  public static void Main()

  {
    var a = produceIndices();
    var b = queryIndices();
    Console.WriteLine(a);
  }
}



Item 9: Avoid Conversion Operators in Your APIs

不要搞隐式转换。

static public implicit operator ClassB(ClassA a)

{
    // return b
}



Item 10: Use Optional Parameters to Minimize Method Overloads

void foobar(string firstName, string lastName);

foobar(firstName : "kasicass", lastName : "tang");

如果换了参数名,则使用的地方也需要改。

这条规则就是减少修改参数,防止破坏使用你的库的代码。



Item 11: Understand the Attraction of Small Functions

每个函数,只有在第一次调用的时候才会JIT。所以把逻辑拆成小函数是有好处的。



.NET Resource Management

关于GC,利用finalizer和IDisposable

GC在释放unmanaged object的时候,调用finalizer;何时调用,不可控;且在GC过程中会调用finalizer会导致性能下降。咋办?引入IDisposable。


Item12: Prefer Member Initializers to Assignment Statements

public class MyClass

{
  private List<sring> labels_ = new List<string>();    // good
}

public class MyClass

{
  private List<string> labels_;
  public MyClass()
  {
    labels_ = new List<string>();    // not recommended
  }
}

如果ValueType值为0,则不需要initializer,CLR底层已经默认为0了,再赋值反而降低效率。

MyValType myVal1;  // initialized to 0

MyValType myVal2 = new MyValType(); // also 0, inefficient


如果使用property,则无法使用initializer。

public string Name { get; set; }


如果initializer可能抛出异常,则还得放到构造函数中try catch。



Item 13: Use Proper Initialization for Static Class Members

初始化逻辑简单,使用static initializer就好。初始化逻辑复杂,可以使用static constructor

public class MySingleton

{
  private static readonly MySingleton theOneAndOnly = new MySingleton();
  public static MySingleton TheOnly { get { return theOneAndOnly; } }
  private MySingleton() {}
}

public class MySingleton2

{
  private static readonly MySingleton2 theOneAndOnly;
  static MySingleton2()
  {
    theOneAndOnly = new MySingleton2();
  }

  public static MySingleton2 TheOnly { get { return theOneAndOnly; } }

  private MySingleton2() {}
}

对于static constructor,没有参数,因为是由CLR调用的。

static initializer/constructor 都要处理好异常,如果抛出异常,则整个程序挂掉。



Item 14: Minimize Duplicate Initialization Logic

如果多个constructor中有重复的代码,把它们合并到一个common constructor中。

public class MyClass

{
  private List<ImportantData> recordList;
  private string name;

  public MyClass() : this(0, "") {}

  public MyClass(int initialCount) : this(initialCount, string.Empty) {}
  public MyClass(int initialCount, string name)
  {
    recordList = (initialCount > 0) ?
        new List<ImportantData>(initialCount) :
        new List<ImportantData>();
    this.name = name;
  }
}

C# 4.0 加入了默认参数,所以可以简化为

public class MyClass

{
  private List<ImportantData> recordList;
  private string name;

  public MyClass() : this(0, "") {}

  public MyClass(int initialCount = 0, string name = "")
  {
    recordList = (initialCount > 0) ?
        new List<ImportantData>(initialCount) :
        new List<ImportantData>();
    this.name = name;
  }
}


某类型的instance第一次初始化的顺序

1. Static variable storage is set to 0

2. Static variable initializers execute

3. Static constructors for the base class execute

4. The static constructor executes

5. Instance variable storage is set to 0

6. Instance variable initializers execute

7. The appropriate base class instance constructor executes

8. The instance constructor executes

之后此类型instance的初始化顺序为 5 - 8。



Item 15: Utilize using and try/finally for Resource Cleanup

unmanaged object 通过 IDisposable.Dispose() 来释放,调用者保证。

如果调用者忘记了,finalizer保证。

public void ExecuteCommand(string connString, string commandString)

{
  SqlConnection myConnection = new SqlConnection(connString);
  SqlCommand mySqlCommand = new SqlCommand(commandString, myConnection);

  myConnection.Open();

  mySqlCommand.ExecuteNonQuery();

  mySqlCommand.Dispose();

  myConnection.Dispose();
}

可以用using来保证 Dispose() 一定被调用。用using,其实自动生成了 try finally。

public void ExecuteCommand(string connString, string commandString)

{
  using (SqlConnection myConnection = new SqlConnection(connString))
  {
    using (SqlCommand mySqlCommand = new SqlCommand(commandString, myConnection))
    {
      myConnection.Open();
      mySqlCommand.ExecuteNonQuery();
    }
  }
}

有些object还有个Close()接口,也是用来释放资源的。Close()只是单纯释放资源,而Dispose()除了释放资源,还会通知GC模块,不需要调用此object的finalizer了。所以尽量使用Dispose()。



Item 16: Avoid Creating Unnecessary Objects

对于reference types,局部变量的内存也是在heap上分配的。所以尽量减少reference type的申请/释放。

protected override void OnPaint(PaintEventArgs e)

{
  using (Font myFont = new Font("Arial", 10.0f))
  {
    e.Graphics.DrawString(DateTime.Now.ToString(), myFont, Brushes.Black, new PointF(0,0));
  }
  base.OnPaint(e);
}


上面的做法不好,把局部变量改为data member,减少了申请/释放的开销。

private readonly Font myFont = new Font("Arial", 10.0f);

protected override void OnPaint(PaintEventArgs e)
{
  e.Graphics.DrawString(DateTime.Now.ToString(), myFont, Brushes.Black, new PointF(0,0));
  base.OnPaint(e);
}


对于string,+/+= 这类操作,都是重新构建了一个新的string。

string msg = "Hello, ";

msg += thisUser.Name;
msg += ". Totday is ";
msg += System.DateTime.Now.ToString();


可以用 string.Format, StringBuilder 来减少构建新string的开销。

string msg = string.Format("Hello, {0}. Today is {1}", thisUser.Name, DateTime.Now.ToString());

StringBuilder msg = new StringBuilder("Hello, ");
msg.Append(thisUser.Name);
msg.Append(". Today is ");
msg.Append(DateTime.Now.ToString());
string finalMsg = msg.ToString();



Item 17: Implementat the Standard Dispose Pattern

实现 IDispose 的要求

 1. Freeing all unmanaged resources.

 2. Freeing all managed resources (this includes unhooking events).

 3. Setting a state flag to indicate that the object has been disposed. You need to check this state and throw ObjectDisposed exceptions in your public methods, if any called after disposing of an object.

 4. Suppressing finalization. You call GC.SuppressFinalize(this) to accomplish this task.


正确的 IDispose 实现

public class MyResourceHog : IDisposable

{
  private bool alreadyDisposed = false;
  public void Dispose()
  {
    Dispose(true);
    GC.SuppressFinalize(this);
  }

  protected virtual void Dispose(bool isDisposing)

  {
    if (alreadyDisposed)
      return;

    if (isDisposing)

    {
      // free managed resources here
    }

    // free unmanaged resources here

    alreadyDisposed = true;
  }

  public void ExampleMethod()

  {
    if (alreadyDisposed)
      throw new ObjectDisposedException("MyResourceHog", "Called Example Method on Disposed object");

   // do method stuff

  }
}

public class DerivedResourceHog : MyResourceHog

{
  private bool disposed = false;

  protected override void Dispose(bool isDisposing)

  {
    if (disposed)
      return;

    if (isDisposing)

    {
      // free managed resources.
    }

    // free unmanagned resources.

    base.Dispose(isDisposing);

    disposed = true;
  }
}


上面的例子没有 finalizer,也就是说没地方调 Dispose(false)。只有当拥有 unmananged resources 时,才需要 finalizer。写了 finalizer 是有开销的,所以有需要时才写。

下面是 finalizer 的例子,其实就是 C++ 里面的析构函数。

public MyResourceHog : IDisposable

{
  ~MyResourceHog()
  {
    Dispose(false);
  }

  ...

}



Item 18: Distinguish Between Value Types and Reference Types

value type, struct

reference type, class

Values types store values, and reference types define behavior.

public class C

{
  private MyType a = new MyType();
  private MyType b = new MyType();
}


C cThing = new C();

如果 MyType 是 value type,则 new C() 只有一次allocation,cThing 的大小是两个 MyType。

如果 MyType 是 reference type,则 new C() 有三次allocation,内存消耗是3个指针 + 两个 MyType 的大小。


MyType[] arrayOfTypes = new MyType[100];

如果 MyType 是 value type,则只有一次 allocation,申请了大小为100个MyType的空间。

如果 MyType 是 reference type,则也只有一次 allocation,每个元素都为 null。


其实从 C++ 的角度来看 value type, reference type,是再明了不过的事情。value type 就是 POD,而 reference type 就是 pointer。



Item 19: Ensure That 0 Is a Valid State for Value Types

如果0没有含义,可能让使用者带来困扰。

public enum Plant

{
  Mercury = 1,
  Venus    = 2,
  ...
}

Planet sphere = new Planet();

因为.NET默认值是0,这里 sphere 的值是0。


enum 还会用来做 bit operation,把0只能用来表示“the absence of all flags”,否则语义比较奇怪。

[Flags]

public enum Styles
{
  None = 0,
  Flat = 1,
  Sunken = 2,
  Raised = 4,
}

if ((flag & Styles.Flat) != 0)

  …



Item 20: Prefer Immutable Atomic Value Types

有 public set,使用者可以随时修改对象的值,多线程下不安全。

public struct Foo

{
    public string Name { get; set; }
    public int Age { get; set; }
}


改为这样会好一些,保证初始化后,不能在修改此object的值。

public struct Foo

{
  public string Name { get; private set; }
  public int Age { get; private set; }

  public Foo(string name, int age)

  {
    Name = name;
    Age = age;
  }
}



Expressing Designs in C#


Item 21: Limit Visibility of Your Types

OO最基本的原则,隐藏细节,不需要的地方,不要弄 public method 出来。



Item 22: Prefer Defining and Implementing Interfaces to Inheritance

区分好 interface, abstract class, class 之间的关系和作用



Item 23: Understand How Interface Methods Differ from Virtual Methods

对于 interface 下的 method,不能被 override 的。

interface IMsg

{
  void Message();
}

public class MyClass : IMsg

{
  void Message() { … }
}

public class MyDerivedClass : public MyClass

{
  void Message() { … }
}

MyDerivedClass d = new MyDerivedClass();

d.Message();  // MyDerivedClass.Message()
IMsg m = d as IMsg;
m.Message(); // MyClass.Message()


也可以让 MyDerivedClass 作为 IMsg 的实现者。

public class MyDerivedClass : MyClass

{
  public new void Message() { … }
}

MyDerivedClass d = new MyDerivedClass();

d.Message();  // MyDerivedClass.Message()
IMsg m = d as IMsg;
m.Message(); // MyDerivedClass.Message()
MyClass b = d;
b.Message();  // MyClass.Message()


但 b.Message() 调用的是 MyClass.Message(),也许这并不满足需求。可以这样,让所有的调用都指向 MyDerivedClass.Message()

public class MyClass : IMsg

{
  public virtual void Message() { … }
}

public class MyDerivedClass : MyClass

{

  public override void Message() { … }

}


还可以如下,要求所有继承于 MyClass 的类,都必须实现 Message()。

public abstract class MyClass : IMsg

{
  public abstract void Message();
}


再一种办法是这样

public class MyClass : IMsg

{
  protected virtual void OnMessage() { }
  public void Message()
  {
    OnMessage();
  }
}


对于 base class,它的一切都被子类继承,比如

public class DefaultMessageGenerator

{
  public void Message() { … }
}
public class AnotherMessageGenerator : DefaultMessageGenerator, IMsg
{
}

这样是允许的,IMsg 被满足了。


总之,Interface methods are not virtual methods but a separate contract.



Item 24: Express Callbacks with Delegates

delegate 很好用

List<int> numbers = Enumerable.Range(1, 200).ToList();

var oddNumbers = numbers.Find(n => n % 2 == 1);
var test = numbers.TrueForAll(n => n < 50);
numbers.RemoveAll(n => n % 2 == 0);
numbers.ForEach(item => Console.WriteLine(item));


.NET标准的delegate

Predicate<T>, one bool parameter and a T return

Action<>, any number of parameters and void return

Func<>, any number of parameters and a T result, Func<T, bool> 和 Predicate<T> 类似



Item 25: Implement the Event Pattern for Notifications

C# 有个 keyword 'event',用来原生支持 Event Pattern(Observer Pattern)

public class LoggerEventArgs : EventArgs

{
  public string Message { get; private set; }
  public int Priority { get; private set; }

  public LoggerEventArgs(int p, string m)

  {
    Priority = p;
    Message = m;
  }
}

public class Logger

{
  static Logger()
  {
    theOnly = new Logger();
  }

private Logger()

  {

  }

  private static Logger theOnly = null;

  public static Logger Singleton
  {
    get { return theOnly; }
  }

  public event EventHandler<LoggerEventArgs> Log;

  public void AddMsg(int priority, string msg)

  {
    EventHandler<LoggerEventArgs> l = Log;    // for multi-thread
    if (l != null)
      l(this, new LoggerEventArgs(priority, msg));
  }
}

Logger.Singleton.Log += (sender, msg) => {

    Console.Error.WriteLine("{0}:\t{1}", msg.Priority.ToString(), msg.Message);
  };



Item 26: Avoid Returning References to Internal Class Objects

避免使用者修改对象内部数据,不要返回内部的 reference,比如 List<T> 的 ref。

value type, 安全的

immutable type, 比如 System.String 也是安全的

或者用 System.Collections.ObjectModel.ReadOnlyCollection<T> 来返回容器对象。

public class MyTest

{
    private List<int> myList = new List<int>();
    public ReadOnlyCollection<int> getData()
    {
        return new ReadOnlyCollecton(myList);
    }
}



Item 27: Prefer Making Your Types Serializable

这里 OtherClass 必须支持 Serializable,否则 MyType 不能称为 Serializable

对于 NonSerializable member,.NET 不会帮你初始化,你要自己实现 IDeserializationCallback 去初始化它们。

[Serializable]

public class MyType
{
  private string label;
  [NonSerializable]
  private int cachedValue;
  private OtherClass otherThing;
}


有需要时,还可以直接实现 ISerializable,比如增加了 data member 的时候

using System.Runtime.Serialization;

using System.Security.Permissions;

[Serializable]

public sealed class MyType : ISerializable
{
  private string lanel;
  private OtherClass otherThing;
  private const int DEFAULT_VALUE = 5;
  private int value2;  // added in version 2

  // used only by the Serialization framework

  private MyType(SerializationInfo info, StreamingContext ctx)
  {
    label = info.GetString("label");

    otherThing = (OtherClass)info.GetValue("otherThing", typeof(OtherClass));

    try
    {
      value2 = info.GetInt32("value2");
    }
    catch (SerializationException)
    {
      value2 = DEFAULT_VALUE;  // Found version 1
    }
  }

  [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter=true)]

  void ISerializable.GetObjectData(SerializationInfo info, StreamingContext ctx)
  {
    info.AddValue("label", label);
    info.AddValue("otherThing", otherThing);
    info.AddValue("value2", value2);
  }
}

可惜 Serializable 不能和 autogen property 混用。



Item 28: Create Large-Grain Internet Service APIs

和 C# 没啥关系,说如何做好的接口设计的。



Item 29: Support Generic Covariance and Contravariance

任何继承自 System.Object 的类型,可以转换为 Object。

public class MyType {}

Object o = new MyType();


但对于泛型,就不是这么回事请了。

List<MyType> a = new List<MyType>();

List<Object> b = a;  // Error


但从 C# 4.0 开始,可以支持泛型的继承转换。

public intrface IEnumerable<out T> : IEnumerable

{
  IEnumerator<T> GetEnumerator();
}

public interface IEnumerator<out T> : IDisposable, IEnumerator

{
  T Current { get; }
}

有了 keyword out 之后,可以支持继承转换了。但要求此泛型在使用过程中是readonly的,不能修改。


还有个 keyword in

public interface ICovariantDelegates<out T>

{
  T GetAnItem();
  Func<T> GetAnItemLater();
  void GiveAnItemLater(Action<T> whatToDo);
}
public interface IContravariantDelegates<in T>
{
  void ActOnAnItem(T item);
  void GetAnItemLater(Func<T> item);
  Action<T> ActOnAnItemLater();
}



Working with the Framework


Item 30: Prefer Overrides to Event Handlers

用 override 的方式处理消息事件

public partial class Window1 : Window

{
  public Window1()
  {
    InitializeComponent();
  }

  protected override void OnMouseDown(MouseButtonEventArgs e)

  {
    ...
    base.OnMouseDown(e);
  }
}


用 event 的方式处理消息事件

<Window x:Class="OverridesAndEvent.Window1"

  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300" MouseDown="OnMouseDown">
  <Grid></Grid>
</Window>

public partial class Window1 : Window

{
  public Window1()
  {
    InitializeComponent();
  }

  private void OnMouseDown(object sender, MouseButtonEventArgs e)

  {
    ...
  }
}

用 event 方式处理,如果出现异常,则会打断整个 event list。



Item 31: Implement Ordering Relations with IComparable<T> and IComparer<T>

IComparable<T> 有一个 CompareTo 接口,返回值与 strcmp 一样。

在 .NET 里面,较新的代码用的 IComparable<T>,而一些老代码用了 IComparable,为了兼容性,两者都要实现一下。

public struct Customer : IComparable<Customer>, IComparable

{
  private readonly string name;

  public Customer(string name)

  {
    this.name = name;

  }

  #region IComparable<Customer> Members

  public int CompareTo(Customer other)
  {
    return name.CompareTo(other.name);
  }
  #endregion

  #region IComparable Members

  int IComparable.CompareTo(object obj)
  {
    if (!(obj is Customer))
      throw new ArgumentException("Argument is not a Customer", "obj");

    Customer otherCustomer = (Customer)obj;

    return this.CompareTo(otherCustomer);
  }
  #endregion

  // relational operators

  public static bool operator<(Customer left, Customer right)
  {
    return left.CompareTo(right) < 0;
  }

  public static bool operator<=(Customer left, Customer right)

  {
    return left.CompareTo(right) <= 0;
  }

  public static bool operator>(Customer left, Customer right)

  {
    return left.CompareTo(right) > 0;
  }

  public static bool operator>=(Customer left, Customer right)

  {
    return left.CompareTo(right) >= 0;
  }
}


对于 Equals() 和 operator == 参见 Item 6。排序和相等分开处理,当 CompareTo() 返回 0,而 Equals() 返回 false,也是合理的。

当一个类型没有提供某种排序时,你自己可以利用 IComparer<T> 来实现之。如下,增加 revenue 的比较。

public struct Customer : IComparable<Customer>, IComparable

{
  private readonly string name;
  private double revenue;

  public Customer(string name, double revenue)

  {
    this.name = name;
    this.revenue = revenue;
  }

  ...

  private static RevenueComparer revComp = null;

  // return an object that implements IComparer

  // use lazy evaluation to create just one
  public static IComparer<Customer> RevenueCompare
  {
    get
    {
      if (revComp == null)
        revComp = new RevenueComparer();
      return revComp;
    }   }

  // Class to compare customers by revenue.

  // This is always used via the interface pointer,
  // so only provide the interface override.
  private class RevenueComparer : IComparer<Customer>
  {
    #region IComparer<Customer> Members
    int IComparer<Customer>.Compare(Customer left, Customer right)
    {
      return left.revenue.CompareTo(right.revenue);
    }
    #endregion
  }
}



Item 32: Avoid ICloneable

value type,不需要实现 ICloneable, 直接赋值即可。

reference type,如果基类实现了 ICloneable,则所有子类都需要实现 ICloneable.Clone()


如果子类需要实现 ICloneable,而基类不需要,可以这样

class BaseType

{
  private string label;
  private int[] values;

  protected BaseType()

  {
    label  = "class name";
    values = new int [10];
  }


  // Used by derived values to clone
  protected BaseType(BaseType right)
  {
    label  = right.label;
    values = rieght.values.Clone() as int[];
  }
}


sealed class Derived : BaseType, ICloneable
{
  private double[] dValues;

  public Derived()

  {
    dValues = new double[10];
  }

  // Construct a copy using the base class copy ctor

  private Derived(Derived right) : base(right)
  {
    dValues = right.dValues.Clone() as double[];
  }

  public object Clone()

  {
    Derived rVal = new Derived(this);
    return rVal;
  }
}


总之,尽量避免 ICloneable。



Item 33: Use the new Modifier Only to React to Base Class Updates

子类对基类进行函数重载,则需要加上 new 修饰符,否则会有 warning:“MyDerived.PrintMe()”隐藏了继承的成员“MyBase.PrintMe()”。如果是有意隐藏,请使用关键字 new。

using System;

public class MyBase
{
  public void PrintMe()
  {
    Console.WriteLine("MyBase::PrintMe");
  }
}

public class MyDerived : MyBase

{
  public new void PrintMe()
  {
    Console.WriteLine("MyDerived::PrintMe");
  }
}

public static class MyHello

{
  public static void Main()
  {
    MyBase b = new MyBase();
    b.PrintMe();

    MyDerived d = new MyDerived();

    d.PrintMe();

    b = d;

    b.PrintMe();
  }
}


这应该是对 C++ 的一种改进,显式声明,更佳。



Item 34: Avoid Overloading Methods Defined in Base Classes

overloading, 函数名相同,参数不同。大量使用,容易带来混淆。



Item 35: Learn How PLINQ Implements Parallel Algorithms

使用 PLINQ,虽然看起来很简单。但能理解 PLINQ 的基本运作机制,则能写出更有效率的代码。

int[] arr = new int[] { 1,7,3,5,10,8 };

var nums = from n in arr.AsParallel()
                 where n > 5
                 select n*2;
nums.ForAll(n => Console.WriteLine(n));


关于 partitioning,分割数据。有四种分割算法

按 range 划分,对于 IList<T>, array 按 range 划分。比如 1000 个元素,在 4核机器上,自动划分为250一组

按 chunk 划分,不懂 - -!

改良版的 range 划分,比如 thread_1 处理 0, 4, 8, 12 元素;thread_2 处理 1, 5, 9, 13 元素

按 hash 划分,对于有 Join, GroupJoin, GroupBy, Distinct, Except, Union, Intersect 等操作的,让相同的 item 在同一 thread 种处理。


关于 parallelize tasks,如何并行。

Pipelining,有几个核,就开几个 worker 线程。thread_1 获得 0 元素;thread_2 获得1 元素,以此类推。这里的 0, 1, 2, … 元素是在上面的 partitioning 分割好后的顺序。

stop & go,所有 worker 线程,最终会 join 到 query 发起的那个线程上。query 发起者要等待最终结果,才继续往下执行。对于 ToList(), ToArray(),用此方式。

Inverted Enumeration,节约内存,更快。不懂。。。

var nums = from n in data.AsParallel()

                 where n < 150
                 select Factorial(n);

foreach (var item in nums)    // 每次获取 item 才去 query (lazily)

  Console.WriteLine(item);

// 所谓的 Inverted Enumeration,边 query 边执行 lambda 函数,在 worker 线程中跑 WriteLine()

nums.ForAll(item => Console.WriteLine(item));


下面的例子,把 Enumerable 换成 ParallelEnumberable,就可以看到 SomeTest, SomeProjection 是在 worker 线程去跑的。

public static class MyTest

{

  public static bool SomeTest(this int inputValue)
  {
    Console.WriteLine("testing element: {0}", inputValue);
    return inputValue % 10 == 0;
  }

  public static string SomeProjection(this int input)

  {
    Console.WriteLine("projecting an element: {0}", input);
    return string.Format("Delivered {0} at {1}", input.ToString(), DateTime.Now.ToLongTimeString());
  }

  public static void Main()

  {
    var answers = from n in Enumerable.Range(0, 300)
            where n.SomeTest()
            select n.SomeProjection();
    foreach (var item in answers)
      Console.WriteLine(item);
  }
}



Item 36: Understand How to Use PLINQ for I/O Bound Operations

顺序处理

foreach (var url in urls)

{
  var result = new WebClient().DownloadData(url);
  UseResult(result);
}


使用 Task Parallel Library(TPL),分到worker thread处理

Parallel.ForEach(urls, url =>

{
  var result = new WebClient.().DownloadData(url);
  UseResult(result);
}


还可以用 PLINQ 的写法

var results = from url in urls.AsParallel()

                   select new WebClient().DownloadData(url);
results.ForAll(result => UseResult(result));


Parallel.ForEach 会动态调整线程数,比较适合 I/O bound, CPU bound 混杂的情况。

PLINQ 只根据元素的初始数量,启用固定数量的线程。


再来深入研究下 TPL,比如  RunAsync(taskStarter, taskFinisher)

urls.RunAsync(

  url => startDownload(url),

  task => finishDownload(task.AsyncState.ToString(), task.Result)

);

异步地去跑 taskStarter => taskFinisher,一般 taskStarter 处理异步I/O,而 taskFinisher 处理异步 I/O 获得的结果。


看下 RunAsync 的实现,Task<T>.ContinueWith() 让此 task 结束后,接着执行下一个逻辑。

public static void RunAsync<T, TResult(

  this IEnumerable<T> taskParams,
  Func<T, Task<TResult>> taskStarter,
  Action<Task<TResult>> taskFinisher)
{
  taskParams.Select(parm => taskStarter(parm)).
    asParallel().
    ForAll(t => t.ContinueWith(t2 => taskFinisher(t2)));
}


看看 startDownload() 的实现

private static void finishDownload(string url, byte[] bytes)

{
  Console.WriteLine("Read {0} bytes from {1}", bytes.Length, url);
}

private static Task<byte[]> startDownload(string url)

{
  var tcs = new TaskCompletionSource<byte[]>(url);
  var wc = new WebClient();
  wc.DownloadDataCompleted += (sender, e) =>
  {
    if (e.UserState == tis)
    {
      if (e.Cancelled)
        tcs.TrySetCanceled();
      else if (e.Error != null)
        tcs.TrySetException(e.Error)
      else
        tcs.TrySetResult(e.Result);
    }
  };
  wc.DownloadDataAsync(new Uri(uri), tcs);
  return tcs.Task;
}


.NET 的 Async Programming Model Pattern

IAsyncResult ar = BeginFoo();

result = EndFoo(ar);  // wait async op to finish



Item 37: Construct Parallel Algorithms with Exceptions in Mind

TPL 异步逻辑出现的异常,可以通过 AggregateException 捕获到。AggregateException 就是把所有异常累积起来,一次返回。

try

{
  urls.RunAsync(
    url => startDownload(url),
    task => finishDownload(task.AsyncState.ToString(), task.Result)
  );
}
catch (AggregateException problems)
{
  ReportAggregateError(problems);
}


private static void ReportAggregateError(AggregateException aggregate)
{
  foreach (var exception in aggregate.InnerExceptions)
  {
    if (exception is AggregateException)
      ReportAggregateError(exception as AggregateException);
    else
      Console.WriteLine(exception.Message);
  }
}


如上的方法把所有异常都捕获了,有时我们只想捕获特定的一样。可以这样

try

{
  urls.RunAsync(
    url => startDownload(url),
    task => finishDownload(task.AsyncState.ToString(), task.Result)
  );
}
catch (AggregateException problems)
{
  var handlers = new Dictionary<Type, Action<Exception>>();
  handlers.Add(typeof(WebException), ex => Console.WriteLine(ex.Message));

  if (!HandleAggregateError(problems, handlers))

    throw;

}

private static bool HandleAggregateError(AggregateException aggregate, Dictionary<Type, Action<Exception>> exceptionHandlers)

{
  foreach (var exception in aggregate.InnerExceptions)
  {
    if (exception is AggregateException)
    {
      return HandleAggregateError(exception as AggregateException, exceptionHandlers);
    }
    else if (exceptionHandlers.ContainsKey(exception.GetType()))
    {
      exceptionHandlers[exception.GetType()](exception);
    }
    else
    {
      return false;
    }
  }
  return true;
}


对于 PLINQ, TPL,记得一定要捕捉 AggregateException。



Dynamic Programming in C#


Item 38: Understand the Pros and Cons of Dynamic

除了 var 这种编译时绑定,C# 还有 dynamic,真的运行时绑定类型。

public static dynamic Add(dynamic left, dynamic right)

{

  return left + right;

}

dynamic n = Add(1, 2);

dynamic s = Add("Hello, ", "World");


以及 Expression Tree,可以用来动态生成代码。比如:

public static T AddExpression<T>(T left, T right)

{
  ParameterExpression leftOperand = Expression.Parameter(typeof(T), "left");
  ParameterExpression rightOperand = Expression.Parameter(typeof(T), "right");
  BinaryExpression body = Expression.Add(leftOperand, rightOperand);
  Expression<Func<T, T, T>> adder = Expression.Lambda<Func<T, T, T>>(body, leftOperand, rightOperand);
  Func<T, T, T> theDelegate = adder.Compile();
  return theDelegate(left, right);
}

int n = AddExpression(1, 2);

string s = AddExpression("Hello", "World");


dynamic 和 Expression Tree 都是有运行时开销的,能不用就不用。



Item 39: Use Dynamic to Leverage the Runtime Type of Generic Type Parameters

在做 type cast 的地方,编译期无法明确确定类型时,适当用用 dynamic。



Item 40: Use Dynamic for Parameters That Receive Anonymous Types

函数带 dynamic 参数

public static void WritePricingInformation(dynamic product)

{
  Console.WriteLine("The price of one {0} is {1}", product.Name, product.Price);
}


下面两种类型,都可以丢到 WritePricingInformation() 中。

var price = from n in Inventory

                where n.Cost > 20
                select new { n.Name, Price = n.Cost * 1.15M };

public class DiscountProduct

{
  public static int NumberInInventory { get; set; }

  public double Price { get; set; }

  public string Name { get; set; }

  public string ReasonForDiscount { get; set; }

}



Item 41: Use DynamicObject or IDynamicMetaObjectProvider for Data-Driven Dynamic Types

用 System.Dynamic.DynamicObject,创建 dynamic object。

using System;

using System.Dynamic;
using System.Collections.Generic;
using System.IO;

public class DynamicPropertyBag : DynamicObject

{
  private Dictionary<string, object> storage = new Dictionary<string, object>();

  public override bool TryGetMember(GetMemberBinder binder, out object result)

  {
    if (storage.ContainsKey(binder.Name))
    {
      result = storage[binder.Name];
      return true;
    }

    result = null;

    return false;
  }

  public override bool TrySetMember(SetMemberBinder binder, object value)

  {
    string key = binder.Name;
    if (storage.ContainsKey(key))
      storage[key] = value;
    else
      storage.Add(key, value);
    return true;
  }

  public override string ToString()

  {
    StringWriter message = new StringWriter();
    foreach (var item in storage)
    {
      message.WriteLine("{0}: {1}", item.Key, item.Value);
    }
    return message.ToString();
  }
}

public static class MyTest

{
  public static void Main()
  {
    dynamic v = new DynamicPropertyBag();
    v.Date = DateTime.Now;
    v.Name = "Hello";
    Console.WriteLine(v);
  }
}


如果不能继承 DynamicObject,可以通过实现 IDynamicMetaObjectProvider 来达到目的,但代码更复杂了。



Item 42: Understand How to Make Use of the Expression API

Expression API 复杂,可以动态生成代码,相当于 C# 的 metaprogramming。



Item 43: Use Expressions to Transform Late Binding into Early Binding



Item 44: Minimize Dynamic Objects in Public APIs

尽量少用,Use dynamic only when you must



Miscellaneous


Item 45: Minimize Boxing and Unboxing

用 generic collections,减少 boxing/unboxing



Item 46: Create Complete Application-Specific Exception Classes

定义你自己的 Exception,继承自 System.Exception,类名用 XxxException。实现四个 ctor。

[Serializable]

public class MyAssemblyException : Exception
{
  public MyAssemblyException() : base()
  {
  }

  public MyAssemblyException(string s) : base(s)

  {
  }

  public MyAssemblyException(string s, Exception e) : base(s, e)

  {
  }

  protected MyAssemblyException(SerializationInfo info, StreamingContext ctx) : base(info, ctx)

  {
  }
}


对于第三方库可能抛异常的地方,应该改抛自己的异常。

public double DoSomeWork()

{
  try
  {
    return ThirdPartyLibrary.ImportantRoutine();
  }
  catch (ThreadPartyException e)
  {
    throw new DoingSomeWorkException("Problem with XXX library", e);
  }
}



Item 47: Prefer the Strong Exception Guarantee

exception 会导致程序流程于预期不符,这节讨论了如何保证抛异常了,程序状态尽量还没出错。搞得有点复杂。



Item 48: Prefer Safe Code

为保证安全,不同的 .NET 类库,可能有不同的访问权限 code access security(CAS)。某些权限下,比如 silverlight,是不能访问全部的硬盘的;从web上运行的程序是不能访问注册表的。


System.IO.IsolatedStorage 提供了一个只针对当前 application 的一个可读/写的虚拟目录,与其它 assembly 隔绝,所以是安全的,没有CAS限制。

IsolatedStorageFile iso = IsolatedStorageFile.GetUserStoreForDomain();

IsolatedStorageFileStream myStream = new IsolatedStorageFileStream("SavedStuff.txt", FileMode.Create, iso);
StreamWriter wr = new StreamWriter(myStream);
...
wr.Close();

string[] files = iso.GetFileNames("SavedStuff.txt");

if (files.Length > 0)
{
  StreamReader reader = new StreamReader(new IsolatedStorageFileStream("SavedStuff.txt", FileMode.Open, iso));
  ...
  reader.Close();
}


根据程序的需要,尽量减少有 CAS 要求的代码,将有 CAS 要求的代码放到一个独立的 assembly 中。



Item 49: Prefer CLS-Compliant Assemblies

兼容 Common Language Subsystem(CLS) 让所有.NET语言都可以访问你的 assembly。

C# 写的代码是基于 CLR 的,基本上也就是 CLS 兼容了。而 F#/IronPython 写的代码是基于 DLR 的,要让 C# 调用 F# 写的模块,还需要一些而外工作。


加入这一条,让编译器给你检查是否 CLS 兼容。

[assembly: System.CLSCompliant(true)]


这两条都不是 CLS 兼容的,因为 unsigned int 不符合 CLS。

public UInt32 Foo() { ... }

public void Foo2(Uint32 v) { ... }


并不是所有语言都支持 operator overloading

// preferred C# syntax

public static Foo operator+(Foo left, Foo right)
{
  return Foo.Add(left, right);
}

// static function, desirable for some languages

public static Foo Add(Foo left, Foo right)
{
  return new Foo(left.Bar + right.Bar);
}


CLS兼容 的一些要求

  1. base classes

  2. return values for public and protected mehtods and properties

  3. parameters for public and protected methods and indexers

  4. runtime event arguments

  5. public interfaces, declared or implemented



Item 50: Prefer Smaller, Cohesive Assemblies

Build assemblies that are the right size and contain a small number of public types.

模块设计之基本原则,解耦、正交。

  评论这张
 
阅读(1690)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017