Kapanma uyarısında foreach değişkenine erişim


86

Şu uyarıyı alıyorum:

Kapanışta foreach değişkenine erişim. Derleyicinin farklı sürümleriyle derlendiğinde farklı davranışlara sahip olabilir.

Editörümde şöyle görünüyor:

fareyle üzerine gelindiğinde yukarıda belirtilen hata mesajı

Bu uyarıyı nasıl düzelteceğimi biliyorum ama neden bu uyarıyı alacağımı bilmek istiyorum?

Bu "CLR" versiyonuyla mı ilgili? "IL" ile ilgili mi?



1
TL; DR yanıtı: sorgu ifadenizin sonuna .ToList () veya .ToArray () ekleyin ve uyarıdan kurtulacaktır
JoelFan

Yanıtlar:


136

Bu uyarının iki bölümü vardır. İlk olarak...

Kapanışta foreach değişkenine erişim

... bu kendiliğinden geçersiz değildir, ancak ilk bakışta mantıksızdır. Doğru yapmak da çok zor. (Öyle ki, aşağıda bağlantı verdiğim makale bunu "zararlı" olarak tanımlıyor.)

Sorgunuzu alın, aldığınız kodun temelde C # derleyicisinin (C # 5'ten önce) foreach1 için ürettiği şeyin genişletilmiş bir biçimi olduğuna dikkat edin :

Neden [aşağıdakilerin] geçerli olmadığını [anlamıyorum]:

string s; while (enumerator.MoveNext()) { s = enumerator.Current; ...

Sözdizimsel olarak geçerlidir. Ve sen döngü içinde yapıyoruz bütün kullanıyorsa değerini ait so zaman her şey iyidir. Ancak kapamak ssezgisel olmayan davranışlara yol açacaktır. Aşağıdaki koda bir göz atın:

var countingActions = new List<Action>();

var numbers = from n in Enumerable.Range(1, 5)
              select n.ToString(CultureInfo.InvariantCulture);

using (var enumerator = numbers.GetEnumerator())
{
    string s;

    while (enumerator.MoveNext())
    {
        s = enumerator.Current;

        Console.WriteLine("Creating an action where s == {0}", s);
        Action action = () => Console.WriteLine("s == {0}", s);

        countingActions.Add(action);
    }
}

Bu kodu çalıştırırsanız, aşağıdaki konsol çıktısını alırsınız:

Creating an action where s == 1
Creating an action where s == 2
Creating an action where s == 3
Creating an action where s == 4
Creating an action where s == 5

Beklediğiniz bu.

Muhtemelen beklemediğiniz bir şey görmek için yukarıdaki koddan hemen sonra aşağıdaki kodu çalıştırın :

foreach (var action in countingActions)
    action();

Aşağıdaki konsol çıktısını alacaksınız:

s == 5
s == 5
s == 5
s == 5
s == 5

Neden? Çünkü hepsi aynı şeyi yapan beş işlev yarattık: değerini yazdırın s(kapattığımız). Gerçekte, bunlar aynı işlevdir ("Yazdır s", "Yazdır s", "Yazdır"s " ...).

Onları kullanmaya gittiğimiz noktada, tam olarak istediğimiz şeyi yapıyorlar: değerini yazdırın s. Son bilinen değerine bakarsanız, sbunun olduğunu görürsünüz 5. Böylece s == 5konsola beş kez baskı alıyoruz .

Bu tam olarak istediğimiz şeydi, ama muhtemelen istediğimiz şey değil.

Uyarının ikinci kısmı ...

Derleyicinin farklı sürümleriyle derlendiğinde farklı davranışlara sahip olabilir.

... ne olduğu. C # 5 ile başlayarak, derleyici bunun şu yolla olmasını "engelleyen" farklı kod üretir:foreach .

Bu nedenle, aşağıdaki kod, derleyicinin farklı sürümleri altında farklı sonuçlar üretecektir:

foreach (var n in numbers)
{
    Action action = () => Console.WriteLine("n == {0}", n);
    countingActions.Add(action);
}

Sonuç olarak, R # uyarısını da üretecektir :)

Yukarıdaki ilk kod parçacığım, kullanmadığım için derleyicinin tüm sürümlerinde aynı davranışı sergileyecektir foreach(bunun yerine, onu C # 5 öncesi derleyicilerin yaptığı şekilde genişlettim).

Bu CLR versiyonu için mi?

Burada ne istediğinden pek emin değilim.

Eric Lippert'in gönderisi, değişikliğin "C # 5'te" gerçekleştiğini söylüyor. Yanimuhtemelen .NET 4.5 veya sonrasını hedeflemeniz gerekir Yeni davranışı elde etmek için C # 5 veya sonraki bir derleyici ile ve bundan önceki her şey eski davranışı alır.

Ancak açık olmak gerekirse, bu .NET Framework sürümünün değil derleyicinin bir işlevidir.

IL ile bir alakası var mı?

Farklı kod farklı IL üretir, dolayısıyla bu anlamda üretilen IL için sonuçlar vardır.

1 foreach , yorumunuzda gönderdiğiniz koddan çok daha yaygın bir yapıdır. Sorun tipik olarak foreachmanuel numaralandırma yoluyla değil, kullanımından kaynaklanmaktadır . Bu nedenle foreachC # 5'te yapılan değişiklikler bu sorunu önlemeye yardımcı olur, ancak tamamen değil.


7
Aslında foreach döngüsünü farklı derleyiciler üzerinde aynı hedefi kullanarak farklı sonuçlar elde etmeyi denedim (.Net 3.5). VS2010 (inandığım .net 4.0 ile ilişkili derleyiciyi kullanıyor) ve VS2012 (inandığım .net 4.5 derleyicisi) kullandım. Prensipte bu, VS2013 kullanıyorsanız ve .Net 3.5'i hedefleyen bir projeyi düzenliyorsanız ve bunu biraz daha eski bir çerçeve kurulu olan bir yapı sunucusunda oluşturuyorsanız, programınızın makinenizde konuşlandırılan yapıya göre farklı sonuçlarını görebileceğiniz anlamına gelir.
Ykok

İyi yanıt, ancak "foreach" in ne kadar alakalı olduğundan emin değilim. Bu manuel numaralandırma ile veya basit bir for (int i = 0; i <collection.Size; i ++) döngüsünde olmaz mıydı? Kapanışların kapsam dışına çıkması ile ilgili bir sorun gibi görünüyor veya daha doğrusu, kapatmaların içinde tanımlandıkları kapsamın dışına çıktıklarında nasıl davrandıklarını anlamaları ile ilgili bir sorun.
Brad

Buradaki foreachşeyler sorunun içeriğinden geliyor. Bunun çeşitli, daha genel şekillerde olabileceği konusunda haklısın.
ta.speot.is

1
R # neden beni hala uyarıyor, 4.5 olarak belirlediğim hedef çerçeveyi okumuyor.
Johnny_D

1
"Yani muhtemelen .NET 4.5 veya sonrasını hedeflemelisiniz" Bu ifade doğru değil. Hedeflediğiniz .NET sürümünün bunun üzerinde hiçbir etkisi yoktur, derlemek için C # 5 (VS 2012 veya daha yenisi) kullanıyorsanız, davranış .NET 2.0, 3.5 ve 4'te de değişir. Bu nedenle, bu uyarıyı yalnızca .NET 4.0 veya önceki sürümlerde alırsınız, 4.5'i hedeflerseniz uyarıyı almazsınız çünkü 4.5'i C # 4 veya daha eski bir derleyicide derleyemezsiniz.
Scott Chamberlain

12

İlk cevap harika, bu yüzden sadece bir şey eklemem gerektiğini düşündüm.

Uyarıyı alıyorsunuz çünkü, örnek kodunuzda, yansıyanModel'e bir IEnumerable atanıyor, bu yalnızca numaralandırma sırasında değerlendirilecek ve daha geniş kapsamlı bir şeye yansıyan model atadıysanız, numaralandırmanın kendisi döngünün dışında gerçekleşebilir. .

Değiştiysen

...Where(x => x.Name == property.Value)

-e

...Where(x => x.Name == property.Value).ToList()

daha sonra yansıyanModel'e foreach döngüsü içinde belirli bir liste atanır, böylece numaralandırma kesinlikle döngü içinde ve onun dışında gerçekleşmeyeceği için uyarıyı almazsınız.


Bu sorunu benim için çözmeyen çok uzun açıklama okudum, sonra çözen kısa bir açıklama. Teşekkürler!
Charles Clayton

Kabul edilen cevabı okudum ve sadece "değişkenleri bağlamıyorsa bu nasıl kapanış olur?" Diye düşündüm. ama artık değerlendirmenin ne zaman yapılacağıyla ilgili olduğunu anlıyorum, teşekkürler!
Jerome

Evet, bu apaçık evrensel bir çözümdür. Yavaş, bellek yoğun, ancak bence tüm durumlar için gerçekten% 100 çalışıyor.
Al Kepp

8

Blok kapsamlı bir değişken, uyarıyı çözmelidir.

foreach (var entry in entries)
{
   var en = entry; 
   var result = DoSomeAction(o => o.Action(en));
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.