应用泛型和匿名函数打造 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
(完)
没有评论:
发表评论