创建对象时需要考虑是否实现比较器

继上一篇“习惯重载运算符”后,本文继续讲《编写高质量代码改善C#程序的157个建议》一书第10个建议“创建对象时需要考虑是否实现比较器”。喜欢本书请到各大商城购买原书,支持正版。

正文开始:


有对象的地方就会存在比较,在.NET的世界中也一样。举个最简单的例子,在UI中,有一个10个人的Salary列表。根据排序的需要,列表要支持针对基本工资来罗列Salary。这个时候,接口IComparable就会起作用,代码如下所示:

class Salary : IComparable
{
  public string Name { get; set; }
  public int BaseSalary { get; set; }
  public int Bonus { get; set; }

  #region IComparable 成员

  public int CompareTo(object obj)
  {
    Salary staff = obj as Salary;
    if (BaseSalary > staff.BaseSalary)
    {
      return 1;
    }
    else if (BaseSalary == staff.BaseSalary)
    {
      return 0;
    }
    else
    {
      return -1;
    }
    // return BaseSalary.CompareTo(staff.BaseSalary);
  }
  #endregion

}

注意 上面代码中CompareTo方法有一条注释的代码,其实本方法完全可以使用该注释代码代替,因为利用了整型的默认比较方法。此处未使用本注释代码,是为了更好地说明比较器的工作原理。

实现了接口IComparable后,我们就可以根据BaseSalary对Salary进行排序了,代码如下所示:

ArrayList companySalary = new ArrayList();
companySalary.Add(new Salary() { Name = "Mike", BaseSalary = 3000 });
companySalary.Add(new Salary() { Name = "Rose", BaseSalary = 2000 });
companySalary.Add(new Salary() { Name = "Jeffry", BaseSalary = 1000 });
companySalary.Add(new Salary() { Name = "steve", BaseSalary = 4000 });
companySalary.Sort();
foreach (Salary item in companySalary)
{
  Console.WriteLine($"{item.Name}\t Basesalary: " + item.BaseSalary.ToString());
}

上面代码的输出如下:

Jeffry   Basesalary: 1000
Rose     Basesalary: 2000
Mike     Basesalary: 3000
steve    Basesalary: 4000

现在,问题来了:如果不想以基本工资BaseSalary进行排序,而是以奖金Bonus 进行排序,该如何处理呢?这个时候,接口IComparer的作用就体现出来了,可以使用IComparer来实现一个自定义的比较器。如下所示:

class BonusComparer : IComparer
{
  #region Icomparer成员
  public int Compare(object x, object y)
  {
    Salary s1 = x as Salary;
    Salary s2 = y as Salary;
    return s1.Bonus.CompareTo(s2.Bonus);
  }
  #endregion
}

我们在排序的时候为Sort方法提供此比较器,代码如下所示:

ArrayList companySalary = new ArrayList();
companySalary.Add(new Salary() { Name = "Mike", BaseSalary = 3000, Bonus = 1000 });
companySalary.Add(new Salary() { Name = "Rose", BaseSalary = 2000, Bonus = 4000 });
companySalary.Add(new Salary() { Name = "Jeffry", BaseSalary = 1000, Bonus = 6000 });
companySalary.Add(new Salary() { Name = "steve", BaseSalary = 4000, Bonus = 3000 });
companySalary.Sort(new BonusComparer());	// 提供一个非默认的比较器

foreach (Salary item in companySalary)
{
  Console.WriteLine($"Name:{item.Name} \tBaseSalary: {item.BaseSalary.ToString()} \tBonus:{item.Bonus}");
}

输出结果如下:

Name:Mike      BaseSalary: 3000        Bonus:1000
Name:steve     BaseSalary: 4000        Bonus:3000
Name:Rose      BaseSalary: 2000        Bonus:4000
Name:Jeffry    BaseSalary: 1000        Bonus:6000

如果我们稍有经验,就会发现上面的代码使用了一个已经不建议使用的集合类ArrayList(当泛型出来后,就建议尽量不使用所有非泛型集合类)。至于原因,从上面的代码中我们也可以看出端倪。

注意查看代码中的Compare函数,如:

public int Compare(object x, object y)
{
  Salary s1 = x as Salary;
  Salary s2 = y as Salary;
  return s1.Bonus.CompareTo(s2.Bonus);
}

我们发现这个函数进行了转型,这是会影响性能的。如果集合中有成千上万个复杂的实体对象,在排序的时候所耗费掉的性能就是可观的;而泛型的出现,可以避免运行时类型转换。

因此,以上代码中的ArrayList,应该换成List,对应地,我们就该实现IComparable和IComparer。最终的代码应该像下面这样:

class Program
{
  static void Main(string[] args)
  {
    List<Salary> companySalary = new List<Salary>()
    {
      new Salary() { Name = "Mike", BaseSalary = 3000, Bonus = 1000 },
      new Salary() { Name = "Rose", BaseSalary = 2000, Bonus = 4000 },
      new Salary() { Name = "Jeffry", BaseSalary = 1000, Bonus = 6000 },
      new Salary() { Name = "steve", BaseSalary = 4000, Bonus = 3000 }
    };
    companySalary.Sort(new BonusComparer());    // 提供一个非默认的比较器

    foreach (Salary item in companySalary)
    {
      Console.WriteLine($"Name:{item.Name} \tBaseSalary: {item.BaseSalary.ToString()} \tBonus:{item.Bonus}");
    }

    Console.Read();
  }
}


class Salary : IComparable<Salary>
{
  public string Name { get; set; }
  public int BaseSalary { get; set; }
  public int Bonus { get; set; }

  #region IComparable<Salary> 成员

  public int CompareTo(Salary other)
  {
    return BaseSalary.CompareTo(other.BaseSalary);
  }
  #endregion

}

class BonusComparer : IComparer<Salary>
{
  #region Icomparer<Salary> 成员

  public int Compare(Salary x, Salary y)
  {
    return x.Bonus.CompareTo(y.Bonus);
  }

  #endregion
}

站长小窍门:其中比较器我很少单独写成一个类的方式,喜欢用Linq的方式,看下面的代码,也更清爽点:

//companySalary.Sort(new BonusComparer());    // 提供一个非默认的比较器
companySalary.Sort((x, y) => x.Bonus.CompareTo(y.Bonus)); // 简写,Linq的写法很舒服

下一篇我们接着读"建议11:区别对待==和Equals",欢迎关注微信公众号【小市民在西河】。

原文出处:微信公众号【小市民在西河】

原文链接:

本文观点不代表Dotnet9立场,转载请联系原作者。

发表评论

登录后才能评论