区别于不使用LINQ to Objects

时间:2009-09-02 03:50:16

标签: c# .net linq iequatable iequalitycomparer

class Program
{
    static void Main(string[] args)
    {
        List<Book> books = new List<Book> 
        {
            new Book
            {
                Name="C# in Depth",
                Authors = new List<Author>
                {
                    new Author 
                    {
                        FirstName = "Jon", LastName="Skeet"
                    },
                     new Author 
                    {
                        FirstName = "Jon", LastName="Skeet"
                    },                       
                }
            },
            new Book
            {
                Name="LINQ in Action",
                Authors = new List<Author>
                {
                    new Author 
                    {
                        FirstName = "Fabrice", LastName="Marguerie"
                    },
                     new Author 
                    {
                        FirstName = "Steve", LastName="Eichert"
                    },
                     new Author 
                    {
                        FirstName = "Jim", LastName="Wooley"
                    },
                }
            },
        };


        var temp = books.SelectMany(book => book.Authors).Distinct();
        foreach (var author in temp)
        {
            Console.WriteLine(author.FirstName + " " + author.LastName);
        }

        Console.Read();
    }

}
public class Book
{
    public string Name { get; set; }
    public List<Author> Authors { get; set; }
}
public class Author
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public override bool Equals(object obj)
    {
        return true;
        //if (obj.GetType() != typeof(Author)) return false;
        //else return ((Author)obj).FirstName == this.FirstName && ((Author)obj).FirstName == this.LastName;
    }

}

这基于“LINQ in Action”中的示例。代码清单4.16。

这两次打印Jon Skeet。为什么?我甚至尝试在Author类中重写Equals方法。仍然不同似乎没有用。我错过了什么?

编辑: 我也添加了==和!=运算符重载。仍然没有帮助。

 public static bool operator ==(Author a, Author b)
    {
        return true;
    }
    public static bool operator !=(Author a, Author b)
    {
        return false;
    }

10 个答案:

答案 0 :(得分:138)

LINQ Distinct在自定义对象方面并不聪明。

所有这一切都是查看你的列表并看到它有两个不同的对象(它们并不关心它们对于成员字段具有相同的值)。

一种解决方法是实现IEquatable接口,如图here所示。

如果您修改了您的作者类,它应该可以工作。

public class Author : IEquatable<Author>
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public bool Equals(Author other)
    {
        if (FirstName == other.FirstName && LastName == other.LastName)
            return true;

        return false;
    }

    public override int GetHashCode()
    {
        int hashFirstName = FirstName == null ? 0 : FirstName.GetHashCode();
        int hashLastName = LastName == null ? 0 : LastName.GetHashCode();

        return hashFirstName ^ hashLastName;
    }
}

Try it as DotNetFiddle

答案 1 :(得分:62)

Distinct()方法检查引用类型的引用相等性。这意味着它在字面上寻找相同的对象,而不是包含相同值的不同对象。

有一个overload需要IEqualityComparer,因此您可以指定不同的逻辑来确定给定的对象是否等于另一个。

如果希望Author通常表现得像普通对象(即只是引用相等),但为了通过名称值检查相等性,请使用 IEqualityComparer 。如果您始终希望根据名称值比较Author对象,则覆盖GetHashCode和Equals ,或实现IEquatable

IEqualityComparer界面上的两位成员是EqualsGetHashCode。您确定两个Author对象是否相等的逻辑似乎是First和Last名称字符串相同。

public class AuthorEquals : IEqualityComparer<Author>
{
    public bool Equals(Author left, Author right)
    {
        if((object)left == null && (object)right == null)
        {
            return true;
        }
        if((object)left == null || (object)right == null)
        {
            return false;
        }
        return left.FirstName == right.FirstName && left.LastName == right.LastName;
    }

    public int GetHashCode(Author author)
    {
        return (author.FirstName + author.LastName).GetHashCode();
    }
}

答案 2 :(得分:39)

没有实施IEquatableEqualsGetHashCode的另一个解决方案是使用LINQ {1}}方法并从IGrouping中选择第一项。

GroupBy

答案 3 :(得分:20)

Distinct()对枚举中的对象执行默认的相等比较。如果您没有覆盖Equals()GetHashCode(),那么它会使用object上的默认实现,它会比较引用。

简单的解决方案是将Equals()GetHashCode()正确实现添加到参与您正在比较的对象图(即Book和Author)的所有类中。

IEqualityComparer界面是一种便利,当您无法访问需要比较的类的内部时,可以在单独的类中实现Equals()GetHashCode() ,或者如果您使用不同的比较方法。

答案 4 :(得分:19)

还有一种方法可以从用户定义的数据类型列表中获取不同的值:

YourList.GroupBy(i => i.Id).Select(i => i.FirstOrDefault()).ToList();

当然,它会提供不同的数据集

答案 5 :(得分:10)

你已经覆盖了Equals(),但要确保你也覆盖了GetHashCode()

答案 6 :(得分:7)

以上答案错了! MSDN上声明的区别是返回默认的Equator,其声明为 Default属性检查类型T是否实现System.IEquatable接口,如果是,则返回使用该实现的EqualityComparer。 否则,它返回一个EqualityComparer,它使用由T 提供的Object.Equals和Object.GetHashCode的覆盖

这意味着只要你超越Equals就可以了。

您的代码无效的原因是您检查firstname == lastname。

请参阅https://msdn.microsoft.com/library/bb348436(v=vs.100).aspxhttps://msdn.microsoft.com/en-us/library/ms224763(v=vs.100).aspx

答案 7 :(得分:0)

您可以在列表中使用扩展方法,该方法根据计算得出的哈希值检查唯一性。 您还可以更改扩展方法以支持IEnumerable。

示例:

public class Employee{
public string Name{get;set;}
public int Age{get;set;}
}

List<Employee> employees = new List<Employee>();
employees.Add(new Employee{Name="XYZ", Age=30});
employees.Add(new Employee{Name="XYZ", Age=30});

employees = employees.Unique(); //Gives list which contains unique objects. 

扩展方法:

    public static class LinqExtension
        {
            public static List<T> Unique<T>(this List<T> input)
            {
                HashSet<string> uniqueHashes = new HashSet<string>();
                List<T> uniqueItems = new List<T>();

                input.ForEach(x =>
                {
                    string hashCode = ComputeHash(x);

                    if (uniqueHashes.Contains(hashCode))
                    {
                        return;
                    }

                    uniqueHashes.Add(hashCode);
                    uniqueItems.Add(x);
                });

                return uniqueItems;
            }

            private static string ComputeHash<T>(T entity)
            {
                System.Security.Cryptography.SHA1CryptoServiceProvider sh = new System.Security.Cryptography.SHA1CryptoServiceProvider();
                string input = JsonConvert.SerializeObject(entity);

                byte[] originalBytes = ASCIIEncoding.Default.GetBytes(input);
                byte[] encodedBytes = sh.ComputeHash(originalBytes);

                return BitConverter.ToString(encodedBytes).Replace("-", "");
            }

答案 8 :(得分:0)

以下代码中的 Equal 运算符不正确。

public bool Equals(Author other)
{
    if (FirstName == other.FirstName && LastName == other.LastName)
        return true;

    return false;
}

public override bool Equals(Object obj)
{
    var other = obj as Author;

    if (other is null)
    {
        return false;
    }

    if (FirstName == other.FirstName && LastName == other.LastName)
        return true;

    return false;
}

答案 9 :(得分:-1)

您可以通过两种方式实现这一目标:

1。。您可以实现Enumerable.Distinct Method所示的IEquatable接口,也可以看到@skalb's answer at this post

2。。如果您的对象没有唯一键,则可以使用GroupBy方法来获得不同的对象列表,必须对对象的所有属性进行分组,然后再选择第一个对象。

例如如下所示,并为我工作:

var distinctList= list.GroupBy(x => new {
                            Name= x.Name,
                            Phone= x.Phone,
                            Email= x.Email,
                            Country= x.Country
                        }, y=> y)
                       .Select(x => x.First())
                       .ToList()

MyObject类如下:

public class MyClass{
       public string Name{get;set;}
       public string Phone{get;set;}
       public string Email{get;set;}
       public string Country{get;set;}
}

3。。如果您的对象具有唯一键,则只能在分组依据下使用它。

例如,我对象的唯一键是ID。

var distinctList= list.GroupBy(x =>x.Id)
                      .Select(x => x.First())
                      .ToList()
相关问题