为基于字典的类创建多个自定义比较器

时间:2018-10-17 12:34:38

标签: c#

我希望在课堂上从字典中返回列表,但允许使用预先编写的比较方法进行自定义排序。在我要转换的原始Java代码中,我在类中使用Google Guava Ordering创建了compare方法,然后有一个称为以下方法的方法传入了一种公共比较器方法,其声明如下:

public List<Word> getWords(Comparator c) { }

我正在尝试在C#中重新创建它,但我不知道怎么做。本质上,在下面的代码中,您可以看到每种类型的排序都有三个版本,此外,我最终为每个返回值创建了两个列表,这似乎有点浪费。

我研究了创建委托,但是有点迷茫,然后想到可以创建一个IComparable,但随后看到了IComparator,然后看到Sort方法采用了Comparator。

有人可以指出以最佳方式将其转换为单一类型的“ GetWords”的方向,从而允许客户调用GetWords来从预先提供的排序集中检索排序列表。

public partial class WordTable
{
    private Dictionary<string, Word> words;

    public WordTable()
    {
        //for testing
        words = new Dictionary<string, Word>();
        words.Add("B", new Word("B", WordTypes.Adjective));
        words.Add("A", new Word("A", WordTypes.Noun));
        words.Add("D", new Word("D", WordTypes.Verb));
    }
    public List<Word> GetWords()
    {
        return words.Values.ToList();
    }
    public List<Word> GetWordsByName()
    {
        List<Word> list = words.Values.ToList<Word>();
        return list.OrderBy(word => word.Name).ToList();
    }
    public List<Word> GetWordsByType()
    {
        List<Word> list = words.Values.ToList<Word>();
        return list.OrderBy(word => word.Type).ToList();
    }
}

3 个答案:

答案 0 :(得分:5)

我认为您正在寻找predicates

实际上,您需要一组预定义的谓词(一个用于ByName,一个用于ByType),然后将此谓词传递到GetWords函数中。

答案 1 :(得分:2)

您可以使用两种方法。

IComparer

这与您过去的Java经验紧密相关。

官方方法是使用IComparer<T>link)。

类似于Java示例中的Comparator,这使您可以创建均实现IComparer<Word>接口的不同排序方法,然后可以动态选择排序方法。

作为一个简单的例子:

public class WordNameComparer : IComparer<Word>
{
    public int Compare(Word word1, Word word2)
    {
         return word1.Name.CompareTo(word2.Name);
    }
}

然后您可以这样做:

public List<Word> GetWords(IComparer<Word> comparer)
{
    return words.Values.OrderBy(x => x, comparer).ToList();
}

您可以通过以下方式致电:

var table = new WordTable();

List<Word> sortedWords = table.GetWords(new WordNameComparer());

当然,您可以通过传递不同的IComparer<Word>来更改排序逻辑。


Func参数

根据经验,由于LINQ具有增强的可读性和较低的实现成本,因此这是一种首选方法。

查看最后两个方法,您应该看到唯一可变的部分是用于对数据进行排序的lambda方法。您当然可以将其可变地转换为方法参数:

public List<Word> GetWordsBy<T>(Func<Word,T> orderByPredicate)
{
    return words.Values.OrderBy(orderBy).ToList();
}

由于OrderBy谓词对所选属性使用通用参数(例如,在字符串字段上排序还是int字段?...),因此您必须使此方法通用,但不需要在调用方法时显式使用泛型参数。例如:

var sortedWordsByName =   table.GetWordsBy(w => w.Name);
var sortedWordsByLength = table.GetWordsBy(w => w.Name.Length);
var sortedWordsByType =   table.GetWordsBy(w => w.Type);

请注意,如果选择一个类而不是值类型,则您将不得不为该类创建并传递一个IComparer<>,否则该类本身必须实现IComparable<>因此可以按照您希望的方式对其进行排序。

您可以引入升序/降序:

public List<Word> GetWordsBy<T>(Func<Word,T> orderByPredicate, bool sortAscending = true)
{
    return sortAscending
              ? words.Values.OrderBy(orderBy).ToList()
              ? words.Values.OrderByDescending(orderBy).ToList();
}

更新

  

我正在尝试使用委托,但是避免了调用者不得不滚动自己的lambda语句并使用预定义的语句。

您可以简单地用一些预定义的选项包装您的方法:

public List<Word> GetWordsBy<T>(Func<Word,T> orderByPredicate)
{
    return words.Values.OrderBy(orderBy).ToList();
}

public List<Word> GetWordsByName()
{
    return GetWordsBy(w => w.Name);
}

这样,您的外部呼叫者不需要的话就不必使用lambda;但是您仍然可以在类中保留可重用代码的好处。

有很多方法可以做到这一点。出于可读性考虑,我更喜欢创建预设方法,但您可以改用一个枚举,然后将其映射到正确的Func。或者,您可以创建一些静态预设lambda,供外部调用者参考。或者...世界就是你的牡蛎:-)

答案 2 :(得分:-1)

我希望这行得通,甚至可以编译。

class WordTable
{
    public List<Word> GetWords(IComparer<Word> comparer)
    {
        return words.Values.OrderBy(x => x, comparer).ToList();
    }
}

class WordsByNameAndThenTypeComparer : IComparer<Word>
{
    public override int Compare(Word x, Word y)
    {
        int byName = x.Name.CompareTo(y.Name);

        return byName != 0 ? byName : x.Type.CompareTo(y.Type);
    }
}

用法:

WordTable wt = new WordTable();
List<Words> words = wt.GetWords(new WordsByNameAndThenTypeComparer());