LINQ ऑब्जेक्ट्स के साथ काम नहीं कर रहा है


120
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।

यह जॉन स्कीट को दो बार प्रिंट करता है। क्यों? मैं भी लेखक वर्ग में बराबरी की विधि की कोशिश की है। फिर भी डिस्टिक्ट काम में नहीं लगता। मैं क्या खो रहा हूँ?

संपादित करें: मैंने == और! = ऑपरेटर को भी अधिभारित किया है। फिर भी कोई मदद नहीं की।

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

जवाबों:


159

LINQ डिस्टिक्ट वह स्मार्ट नहीं है जब यह कस्टम ऑब्जेक्ट्स की बात आती है।

यह सब आपकी सूची को देखता है और देखता है कि इसमें दो अलग-अलग ऑब्जेक्ट हैं (यह ध्यान नहीं देता है कि उनके पास सदस्य फ़ील्ड के लिए समान मान हैं)।

एक वर्कअराउंड IEquatable इंटरफ़ेस को लागू करने के लिए है जैसा कि यहां दिखाया गया है

यदि आप अपने लेखक वर्ग को संशोधित करते हैं तो यह काम करना चाहिए।

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;
    }
}

इसे DotNetFiddle के रूप में आज़माएं


22
IEquatable ठीक है लेकिन अधूरा है; आपको हमेशा Object.Equals () और Object.GetHashCode () को एक साथ लागू करना चाहिए; IEquatable <T>। ईक्वल ऑब्जेक्ट को ओवरराइड नहीं करता है। इसलिए, यह गैर-दृढ़ता से टाइप किए गए तुलनाओं को बनाते समय विफल हो जाएगा, जो अक्सर फ्रेमवर्क में होता है और हमेशा गैर-सामान्य संग्रह में होता है।
एंडीएम

तो क्या डिस्टिंक के ओवरराइड का उपयोग करना बेहतर है जो आईईक्वाटीकॉमकॉमर्स <टी> लेता है जैसा कि रेक्स एम ने सुझाव दिया है? मेरा मतलब है कि मुझे क्या करना चाहिए अगर मैं जाल में नहीं पड़ना चाहता।
तन्मय

3
@ तन्मय यह निर्भर करता है। यदि आप चाहते हैं कि लेखक सामान्य रूप से एक सामान्य वस्तु की तरह व्यवहार करे (अर्थात केवल संदर्भ समानता) लेकिन डिस्टिक्ट के उद्देश्य के लिए नाम मानों की जाँच करें, IEqualityComparer का उपयोग करें। यदि आप हमेशा चाहते हैं कि लेखक की वस्तुओं का नाम मानों के आधार पर तुलना की जाए, तो GetHashCode और Equals को ओवरराइड करें या IEquatable को लागू करें।
रेक्स एम।

3
मैंने लागू किया IEquatable(और ओवररोड Equals/ GetHashCode) लेकिन मेरे ब्रेकपॉइंट में से कोई भी लिनक पर इन तरीकों से फायरिंग नहीं कर रहा है Distinct?
पीटरएक्स

2
@PeterX मैंने इस पर भी गौर किया। मेरे पास ब्रेकपॉइंट थे GetHashCodeऔर Equals, वे फ़ॉरच लूप के दौरान हिट हो गए थे। ऐसा इसलिए है क्योंकि var temp = books.SelectMany(book => book.Authors).Distinct();रिटर्न ए का IEnumerableमतलब है कि अनुरोध को तुरंत निष्पादित नहीं किया गया है, यह केवल तब निष्पादित किया जाता है जब डेटा का उपयोग किया जाता है। आप इस फायरिंग का एक उदाहरण चाहते हैं, तो तुरंत, तो जोड़ने .ToList()के बाद .Distinct()और आप में breakpoints देखेंगे Equalsऔर GetHashCodeforeach से पहले।
JabberwockyDecompiler

70

Distinct()संदर्भ प्रकार के लिए विधि चेकों संदर्भ समानता। इसका मतलब यह है कि यह वस्तुतः एक ही वस्तु की नकल करता है, न कि विभिन्न वस्तुओं का जिसमें समान मूल्य होते हैं।

एक अधिभार है जो एक IEqualityComparer लेता है , इसलिए आप यह निर्धारित करने के लिए विभिन्न तर्क निर्दिष्ट कर सकते हैं कि क्या कोई दी गई वस्तु दूसरे के बराबर है।

यदि आप चाहते हैं कि लेखक सामान्य रूप से एक सामान्य वस्तु की तरह व्यवहार करे (अर्थात केवल संदर्भ समानता), लेकिन नाम मूल्यों द्वारा डिस्टिक्ट चेक समानता के प्रयोजनों के लिए, एक IEqualityComparer का उपयोग करें । यदि आप हमेशा चाहते हैं कि लेखक की वस्तुओं का नाम मानों के आधार पर तुलना की जाए, तो GetHashCode और Equals को ओवरराइड करें या IEquatable को लागू करें

IEqualityComparerइंटरफ़ेस पर दो सदस्य हैं Equalsऔर GetHashCode। यह निर्धारित करने के लिए आपका तर्क है कि क्या दो Authorवस्तुएं समान हैं यदि प्रथम और अंतिम नाम तार समान हैं।

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();
    }
}

1
धन्यवाद! आपके GetHashCode () कार्यान्वयन ने मुझे दिखाया कि मैं अभी भी लापता था। मैं {pass-in object} वापस कर रहा था। GetHashCode (), {{संपत्ति की तुलना के लिए उपयोग नहीं किया जा रहा है। GetHashCode ()। इससे फर्क पड़ता है और बताते हैं कि मेरा अभी तक असफल क्यों रहा - दो अलग-अलग संदर्भों में दो अलग-अलग हैश कोड होंगे।
पिलाज़म

44

कार्यान्वयन के बिना एक और समाधान IEquatable, Equalsऔर GetHashCodeLINQs GroupByविधि का उपयोग करना है और IGrouping से पहली वस्तु का चयन करना है।

var temp = books.SelectMany(book => book.Authors)
                .GroupBy (y => y.FirstName + y.LastName )
                .Select (y => y.First ());

foreach (var author in temp){
  Console.WriteLine(author.FirstName + " " + author.LastName);
}

1
इसने मेरी मदद की, सिर्फ प्रदर्शन को देखते हुए, क्या यह उसी गति से प्रदर्शन करता है ?, जैसा कि उपरोक्त तरीकों पर विचार कर रहा है।
बिस्वजीत

लागू करने के तरीकों के साथ इसे जटिल करने की तुलना में बहुत अच्छा है, और यदि EF का उपयोग sql सर्वर को काम सौंप देगा।
Zapnologica

जबकि यह तरीका काम कर सकता है, वहाँ कई चीजों की संख्या के कारण एक प्रदर्शन मुद्दा होगा
बेलश

@ बेलश इसे काम करो तो इसे जल्दी करो। यकीन है कि इस समूहीकरण के लिए और अधिक काम किया जा सकता है। लेकिन कभी-कभी यह जितना आप चाहते हैं उससे अधिक लागू करने के लिए बोझिल है।
Jehof

2
मैं इस समाधान को पसंद करता हूं लेकिन फिर समूह में "नई" वस्तु का उपयोग करके: .GroupBy(y => new { y.FirstName, y.LastName })
डेव डी जोंग

32

उपयोगकर्ता परिभाषित डेटा प्रकार की सूची से अलग-अलग मान प्राप्त करने का एक और तरीका है:

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

निश्चित रूप से, यह डेटा का अलग सेट देगा


21

Distinct()गणना करने योग्य वस्तुओं में डिफ़ॉल्ट समानता की तुलना करता है। यदि आपने ओवरराइड नहीं किया है Equals()और GetHashCode()फिर, यह डिफ़ॉल्ट कार्यान्वयन का उपयोग करता हैobject , जो संदर्भों की तुलना करता है।

सरल समाधान एक जोड़ने के लिए है सही के कार्यान्वयन Equals()और GetHashCode()सभी वर्गों जो वस्तु ग्राफ आप तुलना कर रहे हैं (यानी बुक और लेखक) में भाग लेने के।

IEqualityComparerइंटरफ़ेस एक सुविधा है कि आप को लागू करने की अनुमति देता है Equals()और GetHashCode()एक अलग वर्ग में आप वर्गों आप की तुलना करने की जरूरत है, या आप तुलना का एक अलग पद्धति उपयोग कर रहे हैं के आंतरिक भागों के लिए पहुँच नहीं है जब।


भाग लेने वाली वस्तुओं के बारे में इस शानदार हास्य के लिए बहुत-बहुत धन्यवाद।
सुहैरा

11

आपने बराबरी की है (), लेकिन सुनिश्चित करें कि आपने GetHashCode () को भी ओवरराइड किया है


GetHashCode () पर जोर देने के लिए +1। आधार HashCode कार्यान्वयन को इसमें शामिल न करें<custom>^base.GetHashCode()
दानी

8

उपरोक्त उत्तर गलत हैं !!! MSDN पर कहा गया डिस्टिफ़ैंट, डिफॉल्ट इक्वेटर लौटाता है, जो कहा गया है कि डिफ़ॉल्ट प्रॉपर्टी जाँचती है कि क्या T टाइप करता है System.IEquatable इंटरफ़ेस और, यदि हां, तो एक इक्वेलिटीकम्पैकर देता है जो उस कार्यान्वयन का उपयोग करता है। अन्यथा, यह एक EquityComparer लौटाता है, जो ऑब्जेक्ट के ओवरराइड्स का उपयोग करता है। T और ऑब्जेक्ट। GetHashCode T द्वारा प्रदान किया गया

जिसका मतलब है कि जब तक आप बराबरी पर रहेंगे तब तक आप ठीक हैं।

आपके द्वारा कोड नहीं किए जाने का कारण यह है कि आप firstname == lastname की जांच कर रहे हैं।

देख https://msdn.microsoft.com/library/bb348436(v=vs.100).aspx और https://msdn.microsoft.com/en-us/library/ms224763(v=vs.100).aspx


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("-", "");
            }

-1

आप इसे दो तरीकों से हासिल कर सकते हैं:

1. आप कर सकते हैं IEquatable इंटरफ़ेस को लागू करने के लिए दिखाया के रूप में Enumerable.Distinct विधि या आप देख सकते हैं इस पोस्ट पर @ skalb के जवाब

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. यदि आपकी ऑब्जेक्ट में अद्वितीय कुंजी है, तो आप इसे केवल समूह में उपयोग कर सकते हैं।

उदाहरण के लिए मेरी वस्तु की अद्वितीय कुंजी ईद है।

var distinctList= list.GroupBy(x =>x.Id)
                      .Select(x => x.First())
                      .ToList()
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.