当前位置:  开发笔记 > 编程语言 > 正文

LINQ除了运算符和对象相等

如何解决《LINQ除了运算符和对象相等》经验,为你挑选了2个好方法。

这是我在使用Except运营商时注意到的一个有趣问题:我有一些用户列表,我想从中排除一些用户:

用户列表来自XML文件:

代码如下:

interface IUser
{
     int ID { get; set; }
     string Name { get; set; }
}

class User: IUser
{

    #region IUser Members

    public int ID
    {
        get;
        set;
    }

    public string Name
    {
        get;
        set;
    }

    #endregion

    public override string ToString()
    {
        return ID + ":" +Name;
    }


    public static IEnumerable GetMatchingUsers(IEnumerable users)
    {
         IEnumerable localList = new List
         {
            new User{ ID=4, Name="James"},
            new User{ ID=5, Name="Tom"}

         }.OfType();
         var matches = from u in users
                       join lu in localList
                           on u.ID equals lu.ID
                       select u;
         return matches;
    }
}

class Program
{
    static void Main(string[] args)
    {
        XDocument doc = XDocument.Load("Users.xml");
        IEnumerable users = doc.Element("Users").Elements("User").Select
            (u => new User
                { ID = (int)u.Attribute("id"),
                  Name = (string)u.Attribute("name")
                }
            ).OfType();       //still a query, objects have not been materialized


        var matches = User.GetMatchingUsers(users);
        var excludes = users.Except(matches);    // excludes should contain 6 users but here it contains 8 users

    }
}

当我打电话时,User.GetMatchingUsers(users)我按预期得到2场比赛.问题是,当我打电话时users.Except(matches),匹配的用户根本没有被排除!我期待6个用户ut"排除"包含所有8个用户.

因为我 GetMatchingUsers(IEnumerable users)正在进行的IEnumerable只是返回IUsers其ID的匹配(在这种情况下为2个IUsers),我的理解是默认情况下Except将使用引用相等来比较要排除的对象.这不是Except表现得怎么样?

更有趣的是,如果我使用对象实现对象.ToList()然后获得匹配的用户,并且调用Except,一切都按预期工作!

像这样:

IEnumerable users = doc.Element("Users").Elements("User").Select
            (u => new User
                { ID = (int)u.Attribute("id"),
                  Name = (string)u.Attribute("name")
                }
            ).OfType().ToList();   //explicity materializing all objects by calling ToList()

var matches = User.GetMatchingUsers(users);
var excludes = users.Except(matches);   // excludes now contains 6 users as expected

Except鉴于它的定义,我不明白为什么我需要实现对象的实现IEnumerable

任何建议/见解将不胜感激.



1> Anton Tolmac..:

a)您需要覆盖GetHashCode函数.它必须为相同的IUser对象返回相等的值.例如:

public override int GetHashCode()
{
    return ID.GetHashCode() ^ Name.GetHashCode();
}

b)您需要在实现IUser的类中覆盖object.Equals(object obj)函数.

public override bool Equals(object obj)
{
    IUser other = obj as IUser;
    if (object.ReferenceEquals(obj, null)) // return false if obj is null OR if obj doesn't implement IUser
        return false;
    return (this.ID == other.ID) && (this.Name == other.Name);
}

c)作为(b)的替代,IUser可以继承IEquatable:

interface IUser : IEquatable
...

在这种情况下,用户类需要提供bool Equals(IUser other)方法.

就这样.现在它无需调用.ToList()方法.



2> Jeff Yates..:

我想我知道为什么这不能按预期工作.因为初始用户列表是LINQ表达式,所以每次迭代时都会重新评估它(一次用于GetMatchingUsers执行Except操作时再次使用),因此创建了新的用户对象.这将导致不同的引用,因此没有匹配.使用ToList修复它,因为它只迭代LINQ查询一次,因此引用是固定的.

我已经能够重现你遇到的问题并调查了代码,这似乎是一个非常合理的解释.不过,我还没有证明这一点.

更新
我只是运行测试,但users在调用之前GetMatchingUsers,在该调用中以及之后输出集合.每次输出对象的哈希码,并且每次指示新对象时确实具有不同的值,正如我怀疑的那样.

以下是每个调用的输出:

==> Start
ID=1, Name=Jeff, HashCode=39086322
ID=2, Name=Alastair, HashCode=36181605
ID=3, Name=Anthony, HashCode=28068188
ID=4, Name=James, HashCode=33163964
ID=5, Name=Tom, HashCode=14421545
ID=6, Name=David, HashCode=35567111
<== End
==> Start
ID=1, Name=Jeff, HashCode=65066874
ID=2, Name=Alastair, HashCode=34160229
ID=3, Name=Anthony, HashCode=63238509
ID=4, Name=James, HashCode=11679222
ID=5, Name=Tom, HashCode=35410979
ID=6, Name=David, HashCode=57416410
<== End
==> Start
ID=1, Name=Jeff, HashCode=61940669
ID=2, Name=Alastair, HashCode=15193904
ID=3, Name=Anthony, HashCode=6303833
ID=4, Name=James, HashCode=40452378
ID=5, Name=Tom, HashCode=36009496
ID=6, Name=David, HashCode=19634871
<== End

并且,这是修改后的代码来显示问题:

using System.Xml.Linq;
using System.Collections.Generic;
using System.Linq;
using System;

interface IUser
{
    int ID
    {
        get;
        set;
    }
    string Name
    {
        get;
        set;
    }
}

class User : IUser
{

    #region IUser Members

    public int ID
    {
        get;
        set;
    }

    public string Name
    {
        get;
        set;
    }

    #endregion

    public override string ToString()
    {
        return ID + ":" + Name;
    }


    public static IEnumerable GetMatchingUsers(IEnumerable users)
    {
        IEnumerable localList = new List
         {
            new User{ ID=4, Name="James"},
            new User{ ID=5, Name="Tom"}

         }.OfType();

        OutputUsers(users);
        var matches = from u in users
                      join lu in localList
                          on u.ID equals lu.ID
                      select u;
        return matches;
    }

    public static void OutputUsers(IEnumerable users)
    {
        Console.WriteLine("==> Start");
        foreach (IUser user in users)
        {
            Console.WriteLine("ID=" + user.ID.ToString() + ", Name=" + user.Name + ", HashCode=" + user.GetHashCode().ToString());
        }
        Console.WriteLine("<== End");
    }
}

class Program
{
    static void Main(string[] args)
    {
        XDocument doc = new XDocument(
            new XElement(
                "Users",
                new XElement("User", new XAttribute("id", "1"), new XAttribute("name", "Jeff")),
                new XElement("User", new XAttribute("id", "2"), new XAttribute("name", "Alastair")),
                new XElement("User", new XAttribute("id", "3"), new XAttribute("name", "Anthony")),
                new XElement("User", new XAttribute("id", "4"), new XAttribute("name", "James")),
                new XElement("User", new XAttribute("id", "5"), new XAttribute("name", "Tom")),
                new XElement("User", new XAttribute("id", "6"), new XAttribute("name", "David"))));
        IEnumerable users = doc.Element("Users").Elements("User").Select
            (u => new User
            {
                ID = (int)u.Attribute("id"),
                Name = (string)u.Attribute("name")
            }
            ).OfType();       //still a query, objects have not been materialized


        User.OutputUsers(users);
        var matches = User.GetMatchingUsers(users);
        User.OutputUsers(users);
        var excludes = users.Except(matches);    // excludes should contain 6 users but here it contains 8 users

    }
}

推荐阅读
有风吹过best
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有