2009-03-28 8 views
11

यहाँ एक दिलचस्प मुद्दा जब Except ऑपरेटर का उपयोग कर मैंने देखा है: मैं उपयोगकर्ताओं की सूची है जिसमें से मैं कुछ उपयोगकर्ताओं को बाहर निकालना चाहते:LINQ छोड़कर ऑपरेटर और वस्तु समानता

उपयोगकर्ताओं की सूची एक एक्सएमएल से आ रही है फ़ाइल:

कोड इस प्रकार है:

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<IUser> GetMatchingUsers(IEnumerable<IUser> users) 
    { 
     IEnumerable<IUser> localList = new List<User> 
     { 
      new User{ ID=4, Name="James"}, 
      new User{ ID=5, Name="Tom"} 

     }.OfType<IUser>(); 
     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<IUser> users = doc.Element("Users").Elements("User").Select 
      (u => new User 
       { ID = (int)u.Attribute("id"), 
        Name = (string)u.Attribute("name") 
       } 
      ).OfType<IUser>();  //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 उपयोगकर्ताओं को "बहिष्कृत" करने के बजाय सभी 8 उपयोगकर्ता शामिल हैं।

के बाद से सभी मैं GetMatchingUsers(IEnumerable<IUser> users) में कर रहा हूँ IEnumerable<IUser> ले जा रहा है और सिर्फ लौटने IUsers जिसकी आईडी के मैच (इस मामले में 2 IUsers), मैं समझता हूँ कि डिफ़ॉल्ट रूप से Except वस्तुओं की तुलना के लिए संदर्भ समानता प्रयोग करेंगे बाहर रखा जाना चाहिए। क्या यह Except व्यवहार नहीं करता है?

क्या और भी दिलचस्प है कि अगर मैं .ToList() का उपयोग कर वस्तुओं अमल में लाना और फिर मिलान उन मिलता है, और Except फोन, सब कुछ उम्मीद के रूप में काम करता है!

तो जैसा

:

IEnumerable<IUser> users = doc.Element("Users").Elements("User").Select 
      (u => new User 
       { ID = (int)u.Attribute("id"), 
        Name = (string)u.Attribute("name") 
       } 
      ).OfType<IUser>().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<T> पर परिभाषित के लिए वस्तुओं अमल में लाना करने की जरूरत है चाहिए?

किसी भी सुझाव/अंतर्दृष्टि की सराहना की जाएगी।

उत्तर

10

मुझे लगता है कि मुझे पता है कि यह अपेक्षा के अनुसार काम करने में विफल क्यों होता है। चूंकि प्रारंभिक उपयोगकर्ता सूची एक LINQ अभिव्यक्ति है, इसे फिर से मूल्यांकन किया जाता है जब इसे पुनरावृत्त किया जाता है (एक बार जब GetMatchingUsers में उपयोग किया जाता है और फिर Except ऑपरेशन करते समय) और इसलिए, नए उपयोगकर्ता ऑब्जेक्ट्स बनाए जाते हैं। इससे अलग-अलग संदर्भ होंगे और इसलिए कोई मिलान नहीं होगा। ToList का उपयोग करके इसे ठीक किया जाता है क्योंकि यह केवल एक बार LINQ क्वेरी को पुन: सक्रिय करता है और इसलिए संदर्भ तय किए जाते हैं।

मैं आपके पास मौजूद समस्या को पुन: उत्पन्न करने और कोड की जांच करने में सक्षम हूं, यह एक बहुत ही व्यावहारिक स्पष्टीकरण की तरह लगता है। हालांकि, मैंने इसे अभी तक साबित नहीं किया है।

अद्यतन
मैं सिर्फ GetMatchingUsers को परीक्षण लेकिन कॉल करने से पहले users संग्रह outputting भाग गया, कि कॉल में है, और यह करने के बाद। प्रत्येक बार ऑब्जेक्ट के लिए हैश कोड आउटपुट था और जब भी मुझे संदेह था, तो हर बार नए ऑब्जेक्ट्स को इंगित करते हुए उनके पास अलग-अलग मूल्य होते हैं।

==> 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<IUser> GetMatchingUsers(IEnumerable<IUser> users) 
    { 
     IEnumerable<IUser> localList = new List<User> 
     { 
      new User{ ID=4, Name="James"}, 
      new User{ ID=5, Name="Tom"} 

     }.OfType<IUser>(); 

     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<IUser> 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<IUser> users = doc.Element("Users").Elements("User").Select 
      (u => new User 
      { 
       ID = (int)u.Attribute("id"), 
       Name = (string)u.Attribute("name") 
      } 
      ).OfType<IUser>();  //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 

    } 
} 
+0

यदि ऐसा है, तो क्या "नई" ऑब्जेक्ट्स को GetMatchingUsers में हर बार पारित नहीं किया जाएगा? यह भी विधि परिणाम के रूप में एक क्वेरी देता है और वस्तुओं नहीं। बस मेरे 2 सेंट ... –

+0

नहीं, क्योंकि जब भी इसका उपयोग किया जाता है तो अभिव्यक्ति का मूल्यांकन किया जाता है। मेरे कोड में, जो यह दिखाता है, इसे GetMatchingUsers पर कॉल करने से पहले मेरे आउटपुट द्वारा मूल्यांकन किया जाता है, फिर फिर GetMatchingUSers को कॉल करते समय, और महत्वपूर्ण रूप से, फिर से छोड़कर। –

+0

क्योंकि GetMatchingUsers के मूल्यांकन और दोनों को छोड़कर अपने स्वयं के उदाहरण उत्पन्न करते हैं, वैसे ही आप अपेक्षा करते हुए कार्य करने में विफल रहते हैं। –

2

मुझे लगता है कि आप IEquatable<T> को लागू करना चाहिए अपने खुद के प्रदान करने के लिए बराबर और GetHashCode विधियों।

MSDN से

(Enumerable.Except):

आप कुछ कस्टम डेटा प्रकार की वस्तुओं के दृश्यों की तुलना करना चाहते हैं, तो आप IEqualityComparer < लागू करना (के < (टी>)>) सामान्य आपकी कक्षा में इंटरफ़ेस। निम्नलिखित कोड उदाहरण दिखाता है कि इस इंटरफ़ेस को कस्टम डेटा प्रकार में कैसे कार्यान्वित करें और GetHashCode और Equals विधियों को प्रदान करें।

+0

लेकिन कोड को वह काम करना चाहिए। यह क्यों काम नहीं कर रहा है? –

+0

सीएमएस: मैंने अपने उत्पादन कोड में IEqualtable लागू किया है और यह काम करता है। मैं समझने में असफल रहा हूं कि यह क्यों है कि क्वेरी पर ToList() को स्पष्ट रूप से कॉल करना है GetMatching उपयोगकर्ताओं को कॉल करने से पहले उपयोगकर्ता चर को क्वेरी –

+0

जेफ के रूप में छोड़ने के बजाय वांछित प्रभाव उत्पन्न करता है: मैं स्थानीय सूची से IUsers को वापस नहीं कर रहा हूं I ' ve GetMatchingUser के अंदर बनाया गया है, यह विधि IUsers को मूल IENumerable से लौटाती है, इसलिए संदर्भ अभी भी दृश्यों के पीछे मूल IUser ऑब्जेक्ट्स के लिए होना चाहिए, इसलिए संदर्भ समानता को अपेक्षित के रूप में काम करना चाहिए था! –

12

ए) आपको GetHashCode फ़ंक्शन को ओवरराइड करने की आवश्यकता है। इसे बराबर IUser ऑब्जेक्ट्स के बराबर मान वापस करना होगा। उदाहरण के लिए:

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

ख) आप कक्षाओं कि IUser लागू में object.Equals (वस्तु 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); 
} 

ग) के लिए (ख एक विकल्प के रूप) IUser IEquatable को प्राप्त कर सके:

interface IUser : IEquatable<IUser> 
... 

उपयोगकर्ता वर्ग bool प्रदान करना होगा, इसके बराबर (IUser अन्य) उस मामले में विधि।

यह सब कुछ है। अब यह कॉल किए बिना काम करता है। टोस्टिस्ट() विधि।

संबंधित मुद्दे