जैसा कि वे कहते हैं, शैतान विवरण में है ...
संग्रह गणना के दो तरीकों के बीच सबसे बड़ा अंतर यह है कि foreach
राज्य ForEach(x => { })
करता है , जबकि नहीं करता है।
लेकिन खुदाई को थोड़ा और गहरा करने दें, क्योंकि कुछ ऐसी चीजें हैं जिनके बारे में आपको पता होना चाहिए कि आपके निर्णय को प्रभावित कर सकते हैं, और कुछ ऐसे मामले हैं जिनके बारे में आपको पता होना चाहिए कि किसी भी मामले के लिए कोडिंग करते समय।
List<T>
व्यवहार का निरीक्षण करने के लिए हमारे छोटे से प्रयोग में मदद करता है। इस प्रयोग के लिए, मैं .NET 4.7.2 का उपयोग कर रहा हूं:
var names = new List<string>
{
"Henry",
"Shirley",
"Ann",
"Peter",
"Nancy"
};
foreach
पहले के साथ इस पर पुनरावृति देता है :
foreach (var name in names)
{
Console.WriteLine(name);
}
हम इसमें विस्तार कर सकते हैं:
using (var enumerator = names.GetEnumerator())
{
}
हाथ में प्रगणक के साथ, कवर के तहत हम देख रहे हैं:
public List<T>.Enumerator GetEnumerator()
{
return new List<T>.Enumerator(this);
}
internal Enumerator(List<T> list)
{
this.list = list;
this.index = 0;
this.version = list._version;
this.current = default (T);
}
public bool MoveNext()
{
List<T> list = this.list;
if (this.version != list._version || (uint) this.index >= (uint) list._size)
return this.MoveNextRare();
this.current = list._items[this.index];
++this.index;
return true;
}
object IEnumerator.Current
{
{
if (this.index == 0 || this.index == this.list._size + 1)
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
return (object) this.Current;
}
}
दो चीजें तत्काल स्पष्ट हो जाती हैं:
- हमें अंतर्निहित संग्रह के अंतरंग ज्ञान के साथ एक राज्य वस्तु दी जाती है।
- संग्रह की प्रति उथली प्रति है।
यह निश्चित रूप से किसी भी तरह से धागा सुरक्षित नहीं है। जैसा कि ऊपर बताया गया था, पुनरावृत्ति करते हुए संग्रह को बदलना केवल खराब मोजो है।
लेकिन पुनरावृत्ति के दौरान संग्रह के साथ अमान्य होने की समस्या के बारे में क्या है कि हमारे बाहर के लोग पुनरावृत्ति के दौरान संग्रह के साथ मिल रहे हैं? सर्वश्रेष्ठ प्रथाओं से पता चलता है कि संचालन और पुनरावृत्ति के दौरान संग्रह को संस्करणबद्ध करना, और अंतर्निहित संग्रह में परिवर्तन होने पर पता लगाने के लिए संस्करणों की जांच करना।
यहां वह चीजें हैं जहां चीजें वास्तव में मुखर हैं। Microsoft दस्तावेज़ के अनुसार:
यदि संग्रह में परिवर्तन किए जाते हैं, जैसे कि तत्वों को जोड़ना, संशोधित करना या हटाना, एन्यूमरेटर का व्यवहार अपरिभाषित है।
खैर, इसका क्या मतलब है? उदाहरण के माध्यम से, सिर्फ इसलिए List<T>
कि अपवाद अपवाद को लागू करने का मतलब यह नहीं है कि लागू करने वाले सभी संग्रह IList<T>
समान होंगे। यह Liskov प्रतिस्थापन सिद्धांत का स्पष्ट उल्लंघन प्रतीत होता है:
एक सुपरक्लास की वस्तुएं आवेदन को तोड़े बिना उसके उपवर्गों की वस्तुओं से बदली जा सकेंगी।
एक और समस्या यह है कि प्रगणक को लागू करना चाहिए IDisposable
- इसका मतलब है कि संभावित मेमोरी लीक का एक अन्य स्रोत, न केवल अगर कॉलर इसे गलत करता है, लेकिन यदि लेखक Dispose
पैटर्न को सही तरीके से लागू नहीं करता है।
अंत में, हमारे पास एक जीवन भर का मुद्दा है ... क्या होता है अगर पुनरावृत्ति मान्य है, लेकिन अंतर्निहित संग्रह चला गया है? अब हम क्या थे का एक स्नैपशॉट ... जब आप एक संग्रह और इसके पुनरावृत्तियों के जीवनकाल को अलग करते हैं, तो आप परेशानी पूछ रहे हैं।
अब जांच करें ForEach(x => { })
:
names.ForEach(name =>
{
});
इसका विस्तार होता है:
public void ForEach(Action<T> action)
{
if (action == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
int version = this._version;
for (int index = 0; index < this._size && (version == this._version || !BinaryCompatibility.TargetsAtLeast_Desktop_V4_5); ++index)
action(this._items[index]);
if (version == this._version || !BinaryCompatibility.TargetsAtLeast_Desktop_V4_5)
return;
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
महत्वपूर्ण नोट निम्नलिखित है:
for (int index = 0; index < this._size && ... ; ++index)
action(this._items[index]);
यह कोड किसी भी एन्यूमरेटर (कुछ भी नहीं Dispose
) को आवंटित नहीं करता है , और पुनरावृत्ति करते समय विराम नहीं देता है ।
ध्यान दें कि यह अंतर्निहित संग्रह की उथली प्रतिलिपि भी करता है, लेकिन संग्रह अब समय में एक स्नैपशॉट है। यदि लेखक संग्रह को बदलने या 'बासी' के लिए एक चेक को सही ढंग से लागू नहीं करता है, तो स्नैपशॉट अभी भी मान्य है।
यह किसी भी तरह से आपको आजीवन मुद्दों की समस्या से बचाता नहीं है ... यदि अंतर्निहित संग्रह गायब हो जाता है, तो अब आपके पास एक उथली प्रति है जो इंगित करता है कि क्या था ... लेकिन कम से कम आपको कोई Dispose
समस्या नहीं है अनाथ iterators पर सौदा ...
हां, मैंने कहा कि पुनरावृत्तियों ... कभी-कभी राज्य के लिए इसका लाभकारी होता है। मान लीजिए कि आप एक डेटाबेस कर्सर के लिए कुछ समान बनाए रखना चाहते हैं ... शायद कई foreach
शैली Iterator<T>
का रास्ता तय करना है। मैं व्यक्तिगत रूप से डिजाइन की इस शैली को नापसंद करता हूं क्योंकि बहुत सारे जीवनकाल के मुद्दे हैं, और आप उन संग्रह के लेखकों की अच्छी कृतियों पर भरोसा करते हैं जो आप पर भरोसा कर रहे हैं (जब तक कि आप सचमुच खरोंच से खुद को सब कुछ नहीं लिखते)।
हमेशा एक तीसरा विकल्प होता है ...
for (var i = 0; i < names.Count; i++)
{
Console.WriteLine(names[i]);
}
यह सेक्सी नहीं है, लेकिन इसके दांत मिले ( टॉम क्रूज और फिल्म द फर्म से माफी )
यह आपकी पसंद है, लेकिन अब आप जानते हैं और यह एक सूचित किया जा सकता है।