惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
GbyAI
GbyAI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 三生石上(FineUI控件)
美团技术团队
Last Week in AI
Last Week in AI
WordPress大学
WordPress大学
L
LangChain Blog
雷峰网
雷峰网
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 叶小钗
Engineering at Meta
Engineering at Meta
腾讯CDC
Recent Announcements
Recent Announcements
The Register - Security
The Register - Security
有赞技术团队
有赞技术团队
Blog — PlanetScale
Blog — PlanetScale
博客园 - Franky
博客园 - 司徒正美
The Cloudflare Blog
Google DeepMind News
Google DeepMind News
T
Tailwind CSS Blog
C
Check Point Blog
小众软件
小众软件
V
Visual Studio Blog
V
V2EX
F
Full Disclosure
J
Java Code Geeks
MongoDB | Blog
MongoDB | Blog
罗磊的独立博客
人人都是产品经理
人人都是产品经理
量子位
Apple Machine Learning Research
Apple Machine Learning Research
F
Fortinet All Blogs
Microsoft Security Blog
Microsoft Security Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 【当耐特】
博客园_首页
Y
Y Combinator Blog
N
Netflix TechBlog - Medium
酷 壳 – CoolShell
酷 壳 – CoolShell
Stack Overflow Blog
Stack Overflow Blog
Recorded Future
Recorded Future
G
Google Developers Blog
Vercel News
Vercel News
大猫的无限游戏
大猫的无限游戏
Microsoft Azure Blog
Microsoft Azure Blog
U
Unit 42
爱范儿
爱范儿
Jina AI
Jina AI

博客园 - codingsilence

更新部分字段 NHibernate 都是分号惹的祸(ORA-00911: invalid character) 降低Winform 内存占用 installshiled 创建iis应用程序池和站点 FARPOINT 常见用法 比较Double型数据时的注意事项 WinForm中获取鼠标当前位置 查看SQL Server日志 WinForm winform动态生成菜单 WinForm 从XML中动态加载菜单的示例 WinForm 选择器 C#程序实现动态调用DLL的研究 WCF 的 WebGet 方式 WCF WebGet WebInvoke WCF Jquery 调用 开源选型关注点 在64位Windows的IIS上开启32位程序支持(转) Oracle grant用户授权 ODP.NET开发和部署的相关问题 Client使用c#和odp.net连接server oracle
“轻松加愉快”地实现并使用IComparer接口
codingsilence · 2011-07-21 · via 博客园 - codingsilence
 

SortedList<T>,SortedSet<T>与SortedDictionary<T>都是我们常用的泛型类型。当T是我们自定义的类型时,往往该类型的默认比较行为不是我们所期望的。

例如,我们有如下很老土的Employee类:

   class Employee
   {
      public int Id { get; set; }
      public String Name { get; set; }
      public int Age { get; set; }
   }

如果我们想让Employee类型按照Name字段升序排序,一般有两种做法。一种是让Employee实现IComparable或者IComparable<Employee>接口。不过此时如果我们又希望Employee按照别的方式排序的话,那只能另想办法了,因为IComparable或IComparable<Employee>接口所要求的Compare方法在Employee中只能有一个实现。另一种做法是构造一个比较器来指定Employee的比较规则。其中,比较器需要实现IComparer或IComparer<Employee>接口。

在这个例子中,实现的方式可以是这样的:

  //以Employee的Name字段为比较规则的比较器
   class EmployeeNameComparer:IComparer<Employee>
   {
      public int Compare(Employee x, Employee y)
      {
         return StringComparer.CurrentCulture.Compare(x.Name, y.Name);
      }
   }

我们构造了一个EmployeeNameComparer类型,当我们这样使用SortedList<Employee>的时候:

         //记录Employee电话号码的数据结构(虽然这么做有点傻,不过为了举例子还是将就一下吧)
         var empTelTable = new SortedDictionary<Employee, String>(new EmployeeNameComparer());

这个比较器类型可以指示SortedList,我们希望它以何种方式比较两个Employee对象。

然而,当我们想让Employee按照Id或Age排序的时候,类似地,我们又会需要一个EmployeeAgeComparer和一个EmployeeAgeComparer:

   //以Employee的Age字段为比较规则的比较器
   class EmployeeAgeComparer:IComparer<Employee>
   {
      public int Compare(Employee x, Employee y)
      {
          return x.Age - y.Age;
      }
   }
   //以Employee的Id字段为比较规则的比较器
   class EmployeeIdComparer:IComparer<Employee>
   {
      public int Compare(Employee x, Employee y)
      {
          return x.Id - y.Id;
      }
   }

EmployeeAgeComparer与EmployeeIdComparer的代码几乎一样。作为DRY(Don‘t Repeart Yourself)原则的忠实维护者,我们是不希望这样几乎一模一样的两份代码出现在我们的项目中的。因此是时候想办法重构了。

上面所实现的比较器都在编译的时候就已经确定了它们本身的行为,也就是说它比较两个Employee时所使用的算法在编译时就已经定死了。然而我们真正想要的是一种通用的,能够在运行时指定其行为的比较器。

为了能让同一个比较器能够拥有多种比较的方式,我们需要在运行时将比较的算法传递给它。在比较器的构造函数中向其传递算法,似乎很靠谱。而且在执行构造函数的过程中我们还能够锁定这个算法,以防止在比较器实例化之后,其比较行为还有机会被我们有意或无意地改变。

接下来的问题是如何传递一个表示算法的参数?我们不仅要考虑该参数的有效性,还要考虑它的易用性(我们没有理由为了使用代表某种算法的参数,还需要为它特意构造一种类型吧?那也太麻烦了吧。)。基于这种情况,方法委托自然成了当仁不让的选择!

好了,设计完毕。请看下面的比较器泛型:

   public class DelegatedComparer<T> : IComparer<T>
   {
      //在构造函数结束后,_compare就没法改变了。因此DelegatedComparer<T>实例的行为也就可以被锁定了。
      private readonly Func<T, T, int> _compare;
      
      //传入一个委托,表示比较算法。
      public DelegatedComparer(Func<T, T, int> func)
      {
         _compare = func;
      }

      public int Compare(T x, T y)
      {
         //直接调用委托。
         return _compare(x, y);
      }
   }

这个DelegatedComparer<T>泛型类型实现了IComparer<T>,其构造函数接受一个Func<T, T, int> func类型的委托为参数。这个委托可以用来指定比较器的比较算法。一切就是这么简单!

我们可以这样使用它:

         //记录Employee电话号码的数据结构(虽然这么做有点傻,不过为了举例子还是将就一下吧)
         var empTelTable = new SortedDictionary<Employee, String>(
            new DelegatedComparer<Employee>(
               (x, y) => x.Id - y.Id)
            );

使用起来也非常简单!打字都少了很多吧? :-)

您可能已经看出来了,这不就是Strategy模式吗?您说对了,我们就是利用了Strategy模式可以在运行时改变对象行为的能力,来完成动态指定比较算法的工作的。

有了DelegatedComparer<T>,我们就可以轻松完成Employee对象之间的比较工作了。由于DelegatedComparer<T>是个泛型,所以我们甚至不需要把目光局限在(老土的)Employee身上,我们还可以对Customer类型,Student类型(都同样的老土)使用我们的DelegatedComparer<T>比较器。从此我们再也不需要为了某一种比较算法而专门实现一个类型了!