More Effective C# Item6 : 使用委托定义类型参数上的方法约束

    有时,我们看C#泛型中的约束机制,可能认为会过于简单,因为约束只能制定一个基类、接口、类或者结构和一个无参的构造函数,还有其他很多无法实现,例如无法设置约束,使其必须实现某些方法,或者必须满足某种重载形式的构造函数。

    我们可以考虑下面的问题,如何让泛型类型必须支持Add()方法。有以下两种方式,分别进行讨论。

    第一种方式,按部就班,首先定义一个泛型接口,声明Add()方法,然后让泛型类型实现该接口,代码如下。

1 public interface IAddHandler<T>
2 {
3 T Add(T left, T right);
4 }
5
6  public class AddHandlerForInt : IAddHandler<int>
7 {
8 public int Add(int left, int right)
9 {
10 return left + right;
11 }
12 }

    这种方式,对于使用者来说,是很不方便的,因为它为了支持Add()方法, 必须要实现一个泛型的接口,很容易会使得系统变得过于复杂。

 

    接下来,我们看第二种方案,我们可以指定一个匹配泛型类将要调用的委托的签名,这并不会给泛型类的开发人员带来任何额外的工作,反而却能够为泛型类型的使用者节省大量的时间。

    来看下面的代码。

1 public class FuncTest
2 {
3 public static T Add<T>(T left, T right, System.Func<T, T, T> addFunc)
4 {
5 return addFunc(left, right);
6 }
7 }

    下面是如何使用这个泛型类型。

 

1 public static void AddTest()
2 {
3 Console.Write("1 + 1 = ");
4 Console.WriteLine(FuncTest.Add<int>(1, 1, (x, y) => x + y).ToString());
5 }

    程序运行结果很简单,就不赘述了。

    下面我们来看一个比较复杂的例子,假设有这样的场景,我们现在需要显示很多职员的姓名和年龄,但是这些姓名和年龄是分别放置在两个集合中,如何将这两个集合中的数据,恢复成完整的职员信息。

    来看下面的代码。

代码
1 public class FuncTest
2 {
3 public static T Add<T>(T left, T right, System.Func<T, T, T> addFunc)
4 {
5 return addFunc(left, right);
6 }
7
8 public static IEnumerable<TOutput> Merge<T1, T2, TOutput>(IEnumerable<T1> left, IEnumerable<T2> right, System.Func<T1, T2, TOutput> generator)
9 {
10 IEnumerator<T1> enumLeft = left.GetEnumerator();
11 IEnumerator<T2> enumRight = right.GetEnumerator();
12 while (enumLeft.MoveNext() && enumRight.MoveNext())
13 {
14 yield return generator(enumLeft.Current, enumRight.Current);
15 }
16 }
17 }
18
19  public class Employee
20 {
21 private string m_strName = string.Empty;
22 public string Name
23 {
24 get { return m_strName; }
25 set { m_strName = value; }
26 }
27
28 private int m_nAge = 0;
29 public int Age
30 {
31 get { return m_nAge; }
32 set { m_nAge = value; }
33 }
34
35 public Employee(string name, int age)
36 {
37 m_strName = name;
38 m_nAge = age;
39 }
40
41 public override string ToString()
42 {
43 return string.Format("Name : {0}, Age : {1}", m_strName, m_nAge);
44 }
45 }

    下面是测试代码。

1 public static void MergeTest()
2 {
3 string[] arrName = new string[]{ "Wing", "Unknown"};
4 int[] arrAge = new int[] { 25, 30 };
5 IEnumerable<Employee> enumEmployee = FuncTest.Merge<string, int, Employee>(arrName, arrAge, (name, age) => new Employee(name, age));
6 IEnumerator<Employee> enumerator = enumEmployee.GetEnumerator();
7 while (enumerator.MoveNext())
8 {
9 Console.WriteLine(enumerator.Current.ToString());
10 }
11 }

    上述代码的执行结果,也是很简单的。

 

    上述两个实例中使用到了两个在.NET框架中提供的委托,声明如下。

1 delegate Func<T1, T2, TOutput>();
2  delegate TOutput Func<T1, T2, TOutput>(T1 arg1, T2 arg2);

    当然,在实际项目过程中,我们也可以根据项目的具体需求,定义对应的泛型代理。

 

    通常,最好的设计是使用类约束或者接口约束来指定需要的约束,.NET基础类库中有很多现成的例子,但是,如果你需要仅为某个特定的泛型方法或者类创建自定义的接口契约,那么也可以使用委托将该契约声明成方法的约束,这将会让类型的使用者更加方便,同时泛型类型将更易于使用,易于理解。不要让任何无法由基本约束直接支持的语义上的约束限制了你的设计。

posted @ 2010-06-03 01:01  李潘  阅读(510)  评论(0编辑  收藏  举报