2011-12-13 11 views
5

मैं एक साधारण ForEach विस्तार विधि का परीक्षण करते समय एक अप्रत्याशित परिणाम में भाग गया।क्या कोई कार्य/प्रतिनिधि इसके तर्कों को बदल सकता है?

ForEach विधि

public static void ForEach<T>(this IEnumerable<T> list, Action<T> action) 
{ 
    if (action == null) throw new ArgumentNullException("action"); 

    foreach (T element in list) 
    { 
     action(element); 
    } 
} 

Test विधि

[TestMethod] 
public void BasicForEachTest() 
{ 
    int[] numbers = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 

    numbers.ForEach(num => 
    { 
     num = 0; 
    }); 

    Assert.AreEqual(0, numbers.Sum()); 
} 

क्यों numbers.Sum() 55 और नहीं 0 के बराबर हो सकता है?

उत्तर

5

num वर्तमान तत्व के मूल्य की प्रति है जो आप पुनः सक्रिय कर रहे हैं। तो आप सिर्फ प्रतिलिपि बदल रहे हैं।

आप करते क्या मूल रूप से यह है:

foreach(int num in numbers) 
{ 
    num = 0; 
} 

निश्चित रूप से आप इस सरणी की सामग्री को बदल की उम्मीद नहीं करते?

संपादित: क्या आप चाहते हैं यह है:

for (int i in numbers.Length) 
{ 
    numbers[i] = 0; 
} 

अपने विशिष्ट मामले आप अपने ForEach विस्तार विधि में एक सूचकांक को बनाए रखने और कार्रवाई करने के लिए पारित है कि दूसरा तर्क के रूप में और उसके बाद इस तरह इसका इस्तेमाल कर सकते में :

numbers.ForEachWithIndex((num, index) => numbers[index] = 0); 
सामान्य रूप में

हालांकि: Linq शैली विस्तार तरीकों संग्रह वे पर लागू होते हैं संशोधित बनाना बुरा शैली (IMO) कर रहे हैं। यदि आप एक एक्सटेंशन विधि लिखते हैं जिसे IEnumerable<T> पर लागू नहीं किया जा सकता है तो आपको वास्तव में इसके बारे में कड़ी मेहनत करनी चाहिए यदि आपको वास्तव में इसकी आवश्यकता है (विशेष रूप से जब आप संग्रह को संशोधित करने के इरादे से लिखते हैं)। आपके पास बहुत कुछ हासिल करने के लिए बहुत कुछ नहीं है (अप्रत्याशित साइड इफेक्ट्स की तरह)। मुझे यकीन है कि अपवाद हैं लेकिन मैं उस नियम से चिपक गया हूं और उसने मुझे अच्छी तरह से सेवा दी है।

+0

@ 249076: हाँ यह काम करेगा। –

+0

मैं सहमत हूं कि मैं लूप के लिए क्या चाहता हूं, लेकिन जब कॉल टू एक्शन किया जाता है तो तर्क मूल्य द्वारा पारित किया जाता है, तो मैं इसे एक विस्तार विधि में कैसे काम करूंगा? ऐसा लगता है कि फॉरएच फ़ंक्शन को पास किए गए कार्य प्रतिनिधि के संदर्भ में मान को पास करने का कोई तरीका नहीं है। मुझे लगता है कि हर कोई इसे जानता है, लेकिन मुझे एहसास नहीं हुआ कि foreach (संख्याओं में int num) {num = 0; } काम नहीं करेगा। संख्या एक अस्थायी प्रति क्यों है और संदर्भ नहीं है? मुझे लगता है कि foreach सिर्फ "के लिए" के लिए वाक्य रचनात्मक चीनी था। मुझे लगता है मुझे इतनी सारी धारणाएं रोकने की जरूरत है। – 249076

+0

मैं लिखने के लिए कुछ तरीका समझना चाहता हूं कि प्रत्येक एक्सटेंशन के लिए यह एक int के मान को बदल सकता है। मुझे लगता है कि यह समझ में आता है, लेकिन यह एक पहेली है जिसे मैं हल करना चाहता हूं। – 249076

0

क्योंकि int एक value type है और इसे आपके पैरामीटर पैरामीटर के रूप में विस्तारित किया गया है। इस प्रकार numbers की एक प्रति आपके ForEach विधि को पास की जाती है। सरणी में संग्रहीत मान BasicForEachTest विधि में प्रारंभ किए गए मान कभी संशोधित नहीं होते हैं।

मूल्य स्कीम और मूल्य मानकों पर और अधिक पढ़ने के लिए जॉन स्कीट द्वारा article देखें।

1

क्योंकि संख्या एक प्रति है। क्या तुम करोगी

int i = numbers[0]; 
i = 0; 

आपको लगता है कि संख्या [0] को बदलने की उम्मीद नहीं है,: यह रूप में अगर आप इस कर रहे थे है?

0

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

public delegate void MyActionRef<T>(ref T arg); 
इसी के साथ

, अपने विधि हो जाता है:

public static void ForEach2<T>(this T[] list, MyActionRef<T> actionRef) 
{ 
    if (actionRef == null) 
    throw new ArgumentNullException("actionRef"); 

    for (int idx = 0; idx < list.Length; idx++) 
    { 
    actionRef(ref list[idx]); 
    } 
} 

अब, उपयोग करने के लिए याद अपने परीक्षा पद्धति में ref कीवर्ड:

numbers.ForEach2((ref int num) => 
{ 
    num = 0; 
}); 

यह काम करता है, क्योंकि यह एक सरणी प्रविष्टि ByRef (ref) पारित करने के लिए ठीक है।

public static void ForEach3<T>(this IList<T> list, MyActionRef<T> actionRef) 
{ 
    if (actionRef == null) 
    throw new ArgumentNullException("actionRef"); 

    for (int idx = 0; idx < list.Count; idx++) 
    { 
    var temp = list[idx]; 
    actionRef(ref temp); 
    list[idx] = temp; 
    } 
} 

आशा इस बारे में अपनी समझ में मदद करता है:

आप IList<> बजाय का विस्तार करना चाहते हैं, तो आप क्या करना है।

नोट: मुझे for लूप का उपयोग करना पड़ा। सी # में, foreach (var x in Yyyy) { /* ... */ } में, इसे x को असाइन करने की अनुमति नहीं है (जिसमें x बीरफ (ref या out के साथ) लूप बॉडी के अंदर गुजरना शामिल है।

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