2007年3月23日星期五

应用泛型和匿名函数打造 DictionaryBuilder

编写数据库相关的程序,Dictinary是一个比较常用的工具。例如,在你的系统数据库中有一个“部门”表:

部门ID部门名称部门人数
1信息系统部17
2总务部23
3销售部55
4制造部880
5技术部250

与之对应的 Domain Object 为 Department:

public class Department

{

  private long _id;

  public long id // 部门ID

  {

    get { return _id; }

    set { _id = value; }

  }

  private string _name;

  public string name // 部门名称

  {

    get { return _name; }

    set { _name = value; }

  }

  private int _deployeeCount;

  public int deployeeCount // 部门人数

  {

    get { return _deployeeCount; }

    set { _deployeeCount = value; }

  }

}


假设我们已经从数据库中取得了部门表中的所有数据,并由这些数据创建了5个Department的实体(每条数据一个Department的实体),这些数据存放在一个List对象中,伪代码为:
List<department> departments = 从数据库中取得部门表中的所有数据并创建一个List;
创建一个以部门ID为Key的Dictonary的代码为:

Dictionary<long, Department> id_Department_Dic = new Dictionary<long, Department>();

foreach (Department eachDepartment in departments)

{

  if (!id_Department_Dic.ContainsKey(eachDepartment.id))

  {

    id_Department_Dic.Add(eachDepartment.id, eachDepartment);

  }

}


创建一个以部门名称为Key的Dictionary的代码为:

Dictionary<long, Department> name_Department_Dic = new Dictionary<long, Department>();

foreach (Department eachDepartment in departments)

{

  if (!name_Department_Dic.ContainsKey(eachDepartment.name))

  {

    name_Department_Dic.Add(eachDepartment.name, eachDepartment);

  }

}


看出这两段代码的不同之处了么?没错,仅仅是将两处“id”换成了“name”而已。你是不是已经嗅到了腐朽的气味了呢?让我们来把重复的代码提炼出来吧。

重构

我们希望把创建Dictionary的代码提炼到一个通用的函数中,把Key和Value作为参数传递到这个函数中。第一种方法是使用反射机制,代码类似于这样:

public Dictionary<long, Department> buildDictionary(string keyName, List<Department> src)

{

  foreach (Department eachDepartment in src)

  {

    long id = 通过反射机制从eachDepartment 中取得名为keyName的属性的值;

    if (!id_Department_Dic.ContainsKey(id))

    {

      id_Department_Dic.Add(id, eachDepartment);

    }

  }

}


使用反射的一大缺点是速度很慢,慢到了令人无法忍受的地步。还有一个缺点是作为Key的属性的名称是通过一个string类型的参数传递到函数中的,如果属性名拼写错了,只有在运行时才能发现。所以我们放弃使用这一方法。

第二种方法是使用委托机制。什么是委托呢?如果你熟悉C++,可以将委托当作函数指针来使用。或者,你可以把委托理解为一个类(Class),只不过这个类中定义的不是普通的属性和方法,而是定义了函数的参数个数、参数类型和返回值的类型。

我们可以在类库中新建一个叫DictionaryBuilder的类,它接收从Client传来的List,并委托Client告诉它每个Department的Key是什么。DictionaryBuilder类和Client代码见下图:


使用匿名函数

其实我们不必非得在Client代码中写一个getKey()函数,因为.Net 2.0提供了一个叫“匿名函数”的功能。我们在Client代码中可以直接这样写:

class Program

{

  static void Main(string[] args)

  {

    IList<Department> departments = new List<Department>();

    //Dictionary<long, Department> dic

    //  = new DictionaryBuilder(departments).toDictionary(getKey);

    Dictionary<long, Department> dic

      = new DictionaryBuilder(departments).toDictionary(

      delegate(Department arg)

      {

        return arg.id;

      });

  }

  //private static long getKey(Department arg)

  //{

  //  return arg.id;

  //}

}


匿名函数的原理和妙处留给大家慢慢体会,我就不多说了(其实是我也不知道)。

加上泛型

当然,我们费了半天的劲写的DictionaryBuilder如果只能接收List<Department>就太不划算了,我们的DictionaryBuilder要能够接受任何类型!这就要使用 .Net 2.0 新增的泛型功能。让我们来先看看代码吧:

public class DictionaryBuilder<TKey, TValue>

{

  private ICollection<TValue> _values;

  public delegate TKey GetKey(TValue obj);

  public DictionaryBuilder(ICollection<TValue> values)

  {

    _values = values;

  }

  public Dictionary<TKey, TValue> toDictionary(GetKey getKey)

  {

    Dictionary<TKey, TValue> result =

      new Dictionary<TKey, TValue>();

    foreach (TValue eachValue in _values)

    {

      TKey key = getKey(eachValue);

      if (!result.ContainsKey(key))

      {

        result.Add(key, eachValue);

      }

    }

    return result;

  }

}


觉得有些难懂么?如果你把这段代码与上图中的代码仔细对比一下,就会发现,只不过是把所有的 long 换成了TKey,把所有的Department换成了TValue而已!呵呵,简单吧?单从使用泛型的角度讲,只要把泛型理解成“类型的占位符”就行了。我们的Client代码也需要做一点小小的改变(涂了阴影的部分):

class Program

{

  static void Main(string[] args)

  {

    IList<Department> departments = new List<Department>();

    //Dictionary<long, Department> dic

    //  = new DictionaryBuilder(departments).toDictionary(getKey);

    Dictionary<long, Department> dic

      = new DictionaryBuilder<long, Department>(departments).toDictionary(

      delegate(Department arg)

      {

        return arg.id;

      });

  }

  //private static long getKey(Department arg)

  //{

  //  return arg.id;

  //}

}



好了,到目前为止主要工作均已完成,我的DictionaryBuilder要比上文提到的复杂一点点,可以在这里下载: http://docs.google.com/Doc?id=dgwxq3xg_2fmtd98
(完)

没有评论: