如何让Distinct()与一组自定义对象一起使用

时间:2010-11-19 15:37:04

标签: c# .net linq iequatable

我已按照this post的建议尝试让Distinct()在我的代码中工作,但我仍然遇到问题。以下是我正在使用的两个对象:

    public class InvoiceItem : IEqualityComparer<InvoiceItem>
    {
        public InvoiceItem(string userName, string invoiceNumber, string invoiceAmount)
        {
            this.UserName = userName;
            this.InvoiceNumber= invoiceNumber;
            this.InvoiceAmount= invoiceAmount;
        }
        public string UserName { get; set; }
        public string InvoiceNumber { get; set; }
        public double InvoiceAmount { get; set; }

        public bool Equals(InvoiceItem left, InvoiceItem right)
        {
            if ((object)left.InvoiceNumber == null && (object)right.InvoiceNumber == null) { return true; }
            if ((object)left.InvoiceNumber == null || (object)right.InvoiceNumber == null) { return false; }
            return left.InvoiceNumber == right.InvoiceNumber;
        }


        public int GetHashCode(InvoiceItem item)
        {
            return item.InvoiceNumber == null ? 0 : item.InvoiceNumber.GetHashCode();
        }
    }


    public class InvoiceItems : List<InvoiceItem>{ }

我的目标是使用几千个InvoiceItems对象填充aBunchOfInvoiceItems对象(我们将其称为InvoiceItem),然后执行:

InvoiceItems distinctItems = aBunchOfInvoiceItems.Distinct();  

当我设置此代码并运行它时,我收到一条错误

  

无法将类型'System.Collections.Generic.IEnumerable'隐式转换为'InvoiceReader.Form1.InvoiceItems'。存在显式转换(您是否错过了演员?)

我不明白如何解决这个问题。我应该采取不同的方法吗?非常感谢任何建议。

5 个答案:

答案 0 :(得分:5)

Distinct返回通用IEnumerable<T>。它返回InvoiceItems实例。事实上,在窗帘后面它返回一个代理对象,该对象实现一个只在需要时访问的迭代器(即当你迭代它时)。

您可以通过调用List<>将其明确强制转换为.ToList()。但是仍然需要将其转换为自定义列表类型。最简单的方法可能是拥有一个合适的构造函数,并调用:

public class InvoiceItems : List<InvoiceItem> {
    public InvoiceItems() { }

    // Copy constructor
    public InvoiceItems(IEnumerable<InvoiceItems> other) : base(other) { }
}

// …

InvoiceItems distinctItems = new InvoiceItems(aBunchOfInvoiceItems.Distinct());

答案 1 :(得分:4)

Konrad Rudolph的回答应该解决你的编译问题。这里还有另一个重要的语义正确性问题:你的等式逻辑实际上都不会被使用。

如果未向Distinct提供比较器,则会使用EqualityComparer<T>.Default。这将尝试使用IEquatable<T>接口,如果缺少此接口,则返回Equals(object other)上声明的普通旧object方法。对于散列,它将使用GetHashCode()方法,也在object上声明。由于界面尚未按您的类型实现,并且上述方法都没有被覆盖,因此存在一个很大的问题: Distinct将仅依赖于引用相等,这是不是你想要的。

当想要编写与类型本身分离的相等比较器时,通常使用IEqualityComparer<T>接口。另一方面,当一个类型想要能够将自己的实例与另一个实例进行比较时;它通常实现IEquatable<T>。我建议其中一个:

  1. 获取InvoiceItem来实施IEquatable<InvoiceItem>
  2. 将比较逻辑移至单独的InvoiceItemComparer : IEqualityComparer<InvoiceItem>类型,然后调用invoiceItems.Distinct(new InvoiceItemComparer());
  3. 如果您想快速破解现有代码,可以执行invoiceItems.Distinct(new InvoiceItem());

答案 2 :(得分:3)

很简单,aBunchOfInvoiceItems.Distinct()会返回IEnumerable<InvoiceItem>,而您正试图将其分配给非IEnumerable<InvoiceItem>的内容。

但是,InvoiceItems的基类有一个构造函数,它接受这样的对象,所以你可以使用它:

public class InvoiceItems : List<InvoiceItem>
{
  public InvoiceItems(IEnumerable<InvoiceItem> items)
    base(items){}
}

然后你可以使用:

InvoiceItems distinctItems = new InvoiceItems(aBunchOfInvoiceItems.Distinct());

尽管如此,我认为从List<InvoiceItem>推导出来并没有多大好处,所以我可能更倾向于:

List<InvoiceItem> distinctItems = aBunchOfInvoiceItems.Distinct().ToList();

答案 3 :(得分:1)

该错误与您的班级InvoiceItems有关,该班级继承自List<InvoiceItem>

Distinct返回IEnumerable<InvoiceItem>InvoiceItems是一种非常具体的IEnumerable<InvoiceItem>类型,但任何IEnumerable<InvoiceItem>都不一定是InvoiceItems

一个解决方案可能是使用隐式转换运算符,如果这是你想要做的事情: Doh,完全忘记你不能转换为/从接口转换(感谢Saed)

public class InvoiceItems : List<InvoiceItem>
{
    public InvoiceItems(IEnumerable<InvoiceItem> items) : base(items) { }
}

其他注意事项:

  • 继承List<T>通常很糟糕。而是实施IList<T>
  • 使用列表会抛弃LINQ的一大好处,即懒惰评估。确保预取结果实际上就是你想要做的。

答案 4 :(得分:0)

除了其他答案处理的自定义类vs IEnumerable问题之外,您的代码还有一个主要问题。您的类实现IEqualityComparer而不是IEquatable。使用Distinct时,要过滤的项必须自己实现IEquatable,或者必须使用带有IEqualityComparer参数的重载。按照现在的情况,您对Distinct的调用不会根据您提供的IEqualityComparer Equals和GetHashCode方法过滤项目。

IEqualityComparer应由另一个类实现,而不是被比较的类。如果一个类知道如何比较自己,比如你的InvoiceItem类,它应该实现IEquatable。