2012-01-12 13 views
14

Artech's blog से देखकर और फिर हमने टिप्पणियों में चर्चा की। चूंकि वह ब्लॉग केवल चीनी में लिखा गया है, इसलिए मैं यहां एक संक्षिप्त स्पष्टीकरण ले रहा हूं। कोड पुन: पेश करने:GetHashCode और Equals को System.Attribute में गलत तरीके से लागू किया गया है?

[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] 
public abstract class BaseAttribute : Attribute 
{ 
    public string Name { get; set; } 
} 

public class FooAttribute : BaseAttribute { } 

[Foo(Name = "A")] 
[Foo(Name = "B")] 
[Foo(Name = "C")] 
public class Bar { } 

//Main method 
var attributes = typeof(Bar).GetCustomAttributes(true).OfType<FooAttribute>().ToList<FooAttribute>(); 
var getC = attributes.First(item => item.Name == "C"); 
attributes.Remove(getC); 
attributes.ForEach(a => Console.WriteLine(a.Name)); 

कोड सभी FooAttribute हो जाता है और एक जिसका नाम 'सी' है निकालता है। स्पष्ट रूप से आउटपुट "ए" और "बी" है? अगर सबकुछ आसानी से चल रहा था तो आपको यह सवाल नहीं दिखाई देगा। असल में आपको "एसी" "बीसी" या यहां तक ​​कि सही "एबी" भी मिलेगा (मुझे अपनी मशीन पर एसी मिली, और ब्लॉग लेखक को बीसी मिला)। सिस्टम में GetHashCode/Equals के कार्यान्वयन से समस्या का परिणाम। एट्रिब्यूट। कार्यान्वयन का एक स्निपेट:

[SecuritySafeCritical] 
    public override int GetHashCode() 
    { 
     Type type = base.GetType(); 
 //*****NOTICE***** 
     FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic 
      | BindingFlags.Public 
      | BindingFlags.Instance); 
 object obj2 = null; 
     for (int i = 0; i < fields.Length; i++) 
     { 
      object obj3 = ((RtFieldInfo) fields[i]).InternalGetValue(this, false, false); 
      if ((obj3 != null) && !obj3.GetType().IsArray) 
      { 
       obj2 = obj3; 
      } 
      if (obj2 != null) 
      { 
       break; 
      } 
     } 
     if (obj2 != null) 
     { 
      return obj2.GetHashCode(); 
     } 
     return type.GetHashCode(); 
    } 

यह का उपयोग करता है Type.GetFields तो गुण आधार वर्ग से विरासत में मिली अनदेखी कर रहे हैं, इसलिए FooAttribute के तीन उदाहरणों की समतुल्यता (और फिर Remove विधि एक यादृच्छिक रूप से लेता है)। तो सवाल यह है कि क्या कार्यान्वयन के लिए कोई विशेष कारण है? या यह सिर्फ एक बग है?

+0

मैं कहूंगा कि यह एक बग है, हालांकि मुझे वास्तविक दुनिया परिदृश्य इमेजिंग में कठिनाई है, जहां इससे कोई समस्या होगी। आप इसे connect.microsoft.com पर रिपोर्ट करने पर विचार कर सकते हैं। – Joe

+0

बग बराबर() में है, यह समान मान वापस करने के लिए GetHashCode() के लिए ठीक है। सहमत हैं, कनेक्ट पर इसे पोस्ट करें। मुझे वास्तव में संदेह है कि वे इसे ठीक करेंगे क्योंकि यह एक तोड़ने वाला बदलाव होगा। –

+0

@ हंसपैसेंट: आप सही हैं। यहां मैं 'गेटहाशकोड' का कोड पोस्ट करता हूं क्योंकि लेखक कोड पोस्ट करता है और मेरे पास इस पीसी में कोई असहज नहीं है। –

उत्तर

7

एक स्पष्ट बग, नहीं। एक अच्छा विचार, शायद या शायद नहीं।

एक चीज़ के लिए दूसरे के बराबर होने का क्या अर्थ है? अगर हम वास्तव में चाहते थे तो हम काफी दार्शनिक हो सकते थे।

  1. समानता कर्मकर्त्ता है: पहचान जरूरत पर जोर देता समानता

    केवल थोड़ा दार्शनिक होने के नाते, कुछ चीजें हैं जो पकड़ चाहिए। x.Equals(x) पकड़ना चाहिए।

  2. समानता सममित है। यदि x.Equals(y) फिर y.Equals(x) और !x.Equals(y) तो !y.Equals(x)
  3. समानता संक्रमणीय है। x.Equals(y) और y.Equals(z) तो x.Equals(z) हैं।

कुछ अन्य हैं, हालांकि केवल इन्हें केवल Equals() के लिए कोड द्वारा प्रतिबिंबित किया जा सकता है।

की object.Equals(object), IEquatable<T>.Equals(T), IEqualityComparer.Equals(object, object), IEqualityComparer<T>.Equals(T, T), == या != के ऊपर पूरा नहीं करता है एक ओवरराइड के एक कार्यान्वयन है, यह एक स्पष्ट बग है तो।

अन्य विधि जो .NET में समानता को दर्शाती है object.GetHashCode(), IEqualityComparer.GetHashCode(object) और IEqualityComparer<T>.GetHashCode(T) हैं। यहां सरल नियम है:

यदि a.Equals(b) है तो उसे a.GetHashCode() == b.GetHashCode() रखना होगा। बराबर IEqualityComparer और IEqualityComparer<T> के लिए आयोजित करता है।

अगर ऐसा नहीं रखता है, तो फिर उसमें एक बग मिल गया है।

इसके अलावा, वहाँ कोई ओवर-द सभी नियमों पर क्या समानता मतलब चाहिए।यह अपने Equals() ओवरराइड या समानता तुलनाकर्ता द्वारा लगाए गए वर्ग द्वारा प्रदान की गई कक्षा के अर्थशास्त्र पर निर्भर करता है। बेशक, उन अर्थशास्त्रों को या तो स्पष्ट रूप से स्पष्ट होना चाहिए या अन्यथा कक्षा या समानता तुलनाकर्ता में दस्तावेज किया जाना चाहिए।

सभी में, कैसे एक Equals करता है और/या एक GetHashCode एक बग है:

  1. यह, कर्मकर्त्ता सममित और सकर्मक गुण ऊपर विस्तृत प्रदान करने के लिए विफल रहता है।
  2. यदि GetHashCode और Equals के बीच संबंध ऊपर नहीं है।
  3. यदि यह अपने दस्तावेज़ित अर्थशास्त्र से मेल नहीं खाता है।
  4. यदि यह एक अनुचित अपवाद फेंकता है।
  5. यदि यह एक अनंत लूप में भटक जाता है।
  6. प्रैक्टिस में, यदि अपंग चीजों के रूप में लौटने में इतनी देर लगती है, हालांकि कोई तर्क दे सकता है कि यहां एक सिद्धांत बनाम अभ्यास की बात है।
Attribute पर ओवरराइड साथ

, बराबरी, कर्मकर्त्ता सममित और सकर्मक गुण है, यह GetHashCode यह मेल खाता है, और यह के लिए दस्तावेज़ Equals ओवरराइड है:

इस API का समर्थन करता है। नेट फ्रेमवर्क आधारभूत संरचना और इसका उद्देश्य सीधे आपके कोड से उपयोग नहीं करना है।

आप वास्तव में यह नहीं कह सकते कि आपका उदाहरण इससे वंचित है!

चूंकि आप जिस शिकायत के बारे में शिकायत करते हैं, वह इनमें से किसी भी बिंदु पर असफल नहीं होता है, यह एक बग नहीं है।

var attributes = typeof(Bar).GetCustomAttributes(true).OfType<FooAttribute>().ToList<FooAttribute>(); 
var getC = attributes.First(item => item.Name == "C"); 
attributes.Remove(getC); 

आप पहली बार एक आइटम है कि एक मानदंडों को पूरा करता लिए पूछना, और फिर एक है कि बराबर करने के लिए इसे हटा दिया जाना चाहिए है के लिए पूछना:

इस कोड में एक बग हालांकि नहीं है। getC को हटाए जाने के लिए प्रश्न में टाइप के लिए समानता के अर्थशास्त्र की जांच किए बिना कोई कारण नहीं है।

क्या करना चाहिए है:

bool calledAlready; 
attributes.RemoveAll(item => { 
    if(!calledAlready && item.Name == "C") 
    { 
    return calledAlready = true; 
    } 
}); 

है कि कहने के लिए, हम एक विधेय कि Name == "C" और कोई अन्य के साथ पहली विशेषता से मेल खाता है का उपयोग करें।

+0

उम्मीद है कि 'बराबर' के साथ '==' का कोई संबंध होना चाहिए, आईएमएचओ अनुपयोगी है, जिस सीमा तक '==' के साथ अंतर्निहित प्रकार के व्यवहार में समानता संबंध का प्रतिनिधित्व नहीं होता है (फ़्लोटिंग-पॉइंट प्रकार रिफ्लेक्सिविटी का प्रबंधन भी नहीं करते हैं, और जिस तरह से अंतर्निहित टाइपकास्ट काम करते हैं, 'लंबी' और 'डबल' के बीच तुलना करने का एकमात्र तरीका ट्रांजिटिव होता है, '(लंबा, डबल)' और '(डबल, लम्बा)' ओवरलोड ' == 'शायद फ्रेमवर्क को उन सभी मामलों में समकक्ष संबंध होना चाहिए जहां यह संकलित होता है, लेकिन जब से ऐसा नहीं होता है, तो मुझे लगता है कि यह सर्वोत्तम है ... – supercat

+0

...' == 'और 'बराबर' पूरी तरह से स्वतंत्र अवधारणाएं हो सकती हैं जिनमें कुछ ओवरलैप हो सकता है लेकिन उनके पास कोई वास्तविक संबंध नहीं है। – supercat

0

हाँ, एक बग जैसा कि टिप्पणियों में पहले से ही उल्लेख किया गया है। मैं कुछ संभावित सुधारों का सुझाव दे सकता हूं:

विकल्प 1, विशेषता वर्ग में उत्तराधिकारी का उपयोग न करें, यह डिफ़ॉल्ट कार्यान्वयन को कार्य करने की अनुमति देगा। यह विकल्प यह सुनिश्चित करने के लिए कस्टम तुलनाकर्ता का उपयोग करता है कि आप आइटम को हटाते समय संदर्भ समानता का उपयोग कर रहे हैं। आप आसानी से एक तुलनाकर्ता को लागू कर सकते हैं। तुलना के लिए बस ऑब्जेक्ट। रेफरेंस एक्वाल्स का उपयोग करें और अपने उपयोग के लिए आप टाइप के हैश कोड का उपयोग कर सकते हैं या System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode का उपयोग कर सकते हैं।

public sealed class ReferenceEqualityComparer<T> : IEqualityComparer<T> 
{ 
    bool IEqualityComparer<T>.Equals(T x, T y) 
    { 
     return Object.ReferenceEquals(x, y); 
    } 
    int IEqualityComparer<T>.GetHashCode(T obj) 
    { 
     return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj); 
    } 
} 
+0

इसके बारे में क्या गड़बड़ है? यह या तो 'बराबर' और 'गेटहाशकोड' के लिए सामान्य आवश्यकताओं को पूरा नहीं करता है या फिर मेल नहीं खाता 'Attribute.Equals' के लिए प्रलेखन? –

+0

एक और खोज आयननीय मामला यह तथ्य है कि 'RuntimeHelpers.GetHashCode' काम नहीं करेगा जैसा कि आप इसे विंडोज फोन 7 पर चलाते हैं। यहां "बग रिपोर्ट के लिए एमएस प्रतिक्रिया पर" सीधे उपयोग नहीं किया जा रहा है ... " और वास्तविक दस्तावेज़ों में नहीं। मैं आईएल उत्सर्जित करने की सिफारिश करता हूं जैसे कि मैं इसे करने के बजाय https://github.com/hackcraft/Ariadne/blob/master/Collections/ReferenceEqualityComparer.cs पर करता हूं। –

+0

@ जोन हन्ना, इरादे से एक बग एक फील्ड-वैल्यू तुलना थी और ठीक से काम नहीं करता है। WP7 के लिए, आप सही हैं एपीआई समर्थित नहीं है; हालांकि, ओपी ने WP7 लक्ष्य का कोई उल्लेख नहीं किया है। –

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