Değiştirilmiş Kapanışa Erişim (2)


101

Bu, Erişim'den Değiştirilmiş Kapatma'ya kadar bir soru uzantısıdır . Sadece aşağıdakilerin üretimde kullanım için yeterince güvenli olup olmadığını doğrulamak istiyorum.

List<string> lists = new List<string>();
//Code to retrieve lists from DB    
foreach (string list in lists)
{
    Button btn = new Button();
    btn.Click += new EventHandler(delegate { MessageBox.Show(list); });
}

Yukarıdakileri her başlangıçta yalnızca bir kez çalıştırıyorum. Şimdilik iyi çalışıyor gibi görünüyor. Jon'un bazı durumlarda mantık dışı sonuçlardan bahsettiği gibi. Peki burada neye dikkat etmem gerekiyor? Listenin birden fazla kez çalıştırılması uygun olur mu?


18
Tebrikler, artık Yeniden Paylaşım belgelerinin bir parçasısınız. confluence.jetbrains.net/display/ReSharper/…
Kongress

1
Bu biraz aldatıcıydı, ancak yukarıdaki açıklama benim için bunu açıklığa kavuşturdu: Bu doğru gibi görünebilir, ancak gerçekte, herhangi bir düğmeye tıklandığında yalnızca str değişkeninin son değeri kullanılacaktır. Bunun nedeni, foreach'in bir while döngüsüne girmesidir, ancak yineleme değişkeni bu döngünün dışında tanımlanmıştır. Bu, mesaj kutusunu gösterdiğiniz zaman, str'nin değerinin dizeler koleksiyonundaki son değere zaten yinelenmiş olabileceği anlamına gelir.
DanielV

Yanıtlar:


159

C # 5'ten önce , foreach içinde bir değişkeni yeniden tanımlamanız gerekir - aksi takdirde paylaşılır ve tüm işleyicileriniz son dizeyi kullanır:

foreach (string list in lists)
{
    string tmp = list;
    Button btn = new Button();
    btn.Click += new EventHandler(delegate { MessageBox.Show(tmp); });
}

Önemli bir şekilde, bunun C # 5'ten itibaren değiştiğini ve özellikle bu durumdaforeach artık bunu yapmanız gerekmediğini unutmayın: sorudaki kod beklendiği gibi çalışacaktır.

Bunun bu değişiklik olmadan işe yaramadığını göstermek için aşağıdakileri göz önünde bulundurun:

string[] names = { "Fred", "Barney", "Betty", "Wilma" };
using (Form form = new Form())
{
    foreach (string name in names)
    {
        Button btn = new Button();
        btn.Text = name;
        btn.Click += delegate
        {
            MessageBox.Show(form, name);
        };
        btn.Dock = DockStyle.Top;
        form.Controls.Add(btn);
    }
    Application.Run(form);
}

Yukarıdakileri C # 5'ten önce çalıştırın ve her düğme farklı bir ad gösterse de, düğmelere tıklamak dört kez "Wilma" gösterir.

Bunun nedeni, dil spesifikasyonunun (ECMA 334 v4, 15.8.4) (C # 5'ten önce) şunları tanımlamasıdır:

foreach (V v in x) embedded-statement daha sonra şu şekilde genişletilir:

{
    E e = ((C)(x)).GetEnumerator();
    try {
        V v;
         while (e.MoveNext()) {
            v = (V)(T)e.Current;
             embedded-statement
        }
    }
    finally {
         // Dispose e
    }
}

Değişkenin v(sizin olan list) döngünün dışında bildirildiğine dikkat edin. Dolayısıyla, yakalanan değişkenlerin kurallarına göre, listenin tüm yinelemeleri yakalanan değişken sahibini paylaşacaktır.

C # 5'ten itibaren bu değiştirilir: yineleme değişkeni ( v) döngü içinde kapsama alınır . Bir şartname referansım yok, ancak temelde şöyle oluyor:

{
    E e = ((C)(x)).GetEnumerator();
    try {
        while (e.MoveNext()) {
            V v = (V)(T)e.Current;
            embedded-statement
        }
    }
    finally {
         // Dispose e
    }
}

Yeniden abonelikten çıkma; anonim bir işleyicinin aboneliğini aktif olarak iptal etmek istiyorsanız, işin püf noktası işleyicinin kendisini yakalamaktır:

EventHandler foo = delegate {...code...};
obj.SomeEvent += foo;
...
obj.SomeEvent -= foo;

Aynı şekilde, yalnızca bir kez kullanılabilen bir olay işleyici istiyorsanız (örneğin, Yükle vb.):

EventHandler bar = null; // necessary for "definite assignment"
bar = delegate {
  // ... code
  obj.SomeEvent -= bar;
};
obj.SomeEvent += bar;

Bu artık abonelikten çıkma ;-p


Bu durumda, geçici değişken, temsilciye hizmet etmek için uygulama kapanana kadar bellekte kalacaktır ve değişken çok fazla bellek kullanıyorsa, çok büyük döngüler için bunu yapmanız tavsiye edilmeyecektir. Haklı mıyım
hatalı

1
Olayla ilgili şeyler (düğmeler) olduğu sürece bellekte kalacaktır. Yalnızca tek seferlik delegelerin aboneliğini iptal etmenin bir yolu var, bunu gönderiye ekleyeceğim.
Marc Gravell

2
Ancak amacınıza uygun olarak: evet, yakalanan değişkenler gerçekten de bir değişkenin kapsamını artırabilir. Beklemediğiniz şeyleri yakalamamak için dikkatli olmalısınız ...
Marc Gravell

1
C # 5.0 spesifikasyonundaki değişiklik ile ilgili olarak cevabınızı günceller misiniz? Sadece C # 'daki foreach döngüleri ile ilgili harika bir wiki dokümantasyonu yapmak için. Foreach döngülerini bit.ly/WzBV3L işleyen C # 5.0 derleyicisindeki değişiklikle ilgili bazı iyi cevaplar zaten var , ancak bunlar wiki benzeri kaynaklar değil.
Ilya Ivanov

1
@Kos evet, 5.0'da fordeğişmedi
Marc Gravell
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.