Neden C #, vaka bloklarında yerel kapsam içermiyor?


38

Bu kodu yazıyordum:

private static Expression<Func<Binding, bool>> ToExpression(BindingCriterion criterion)
{
    switch (criterion.ChangeAction)
    {
        case BindingType.Inherited:
            var action = (byte)ChangeAction.Inherit;
            return (x => x.Action == action);
        case BindingType.ExplicitValue:
            var action = (byte)ChangeAction.SetValue;
            return (x => x.Action == action);
        default:
            // TODO: Localize errors
            throw new InvalidOperationException("Invalid criterion.");
    }
}

Ve bir derleme hatası bulmak şaşırttı:

'Action' adlı bir yerel değişken zaten bu kapsamda tanımlandı

Çözülmesi oldukça kolay bir sorundu; Sadece ikinciden kurtulmak variçin hile yaptım.

Açıkça görüldüğü üzere , casebloklar halinde bildirilen değişkenler ebeveynin kapsamına sahiptir switch, ancak bunun neden olduğunu merak ediyorum. C # yürütme (diğer durumlarda üzerinden düşmesine izin vermez göz önüne alındığında gerektirir break , return, throw, veya goto caseher sonunda ifadeleri casebloğu), bu bir iç değişken bildirimleri izin vereceğini oldukça tuhaf görünüyor casebaşka değişkenler ile birlikte kullanıldığında veya çatışma edilecek case. Başka bir deyişle, caseyürütmenin yapamamasına rağmen değişkenlerin ifadelerden geçtiği görülmektedir . C # kafa karıştırıcı veya veya kolayca kötüye kullanılan diğer dillerin bazı yapılarını yasaklayarak okunabilirliği artırmak için büyük acılar çekiyor. Ancak bu karışıklığa neden olması zor gibi görünüyor. Aşağıdaki senaryoları göz önünde bulundurun:

  1. Eğer bunu değiştirecekseydi:

    case BindingType.Inherited:
        var action = (byte)ChangeAction.Inherit;
        return (x => x.Action == action);
    case BindingType.ExplicitValue:
        return (x => x.Action == action);
    

    " Atanmamış yerel değişkenin 'action' kullanımı " alıyorum . Bu kafa karıştırıcı çünkü C # 'daki diğer tüm yapılarda, düşünebildiğim var action = ...değişken değişkeni başlatabilir , ancak burada basitçe bildirir.

  2. Eğer böyle davaları değiştirirsem:

    case BindingType.ExplicitValue:
        action = (byte)ChangeAction.SetValue;
        return (x => x.Action == action);
    case BindingType.Inherited:
        var action = (byte)ChangeAction.Inherit;
        return (x => x.Action == action);
    

    " Yerel değişken 'eylemini' bildirilmeden önce kullanamıyorum " alıyorum . Bu yüzden kasa bloklarının sırası burada tamamen açık olmayan bir şekilde önemli gibi görünüyor - Normalde bunları istediğim herhangi bir sırayla yazabilirim, ancak kullanılan varilk blokta görünmesi gerektiğinden blokları actiondeğiştirmek zorundayım. casebuna göre.

  3. Eğer bunu değiştirecekseydi:

    case BindingType.Inherited:
        var action = (byte)ChangeAction.Inherit;
        return (x => x.Action == action);
    case BindingType.ExplicitValue:
        action = (byte)ChangeAction.SetValue;
        goto case BindingType.Inherited;
    

    Sonra hata alamadım, ancak bir anlamda değişkene ilan edilmeden önce bir değer atanmış gibi görünüyor .
    (Gerçekte bunu yapmak istediğinizi asla düşünemesem de - goto casebugünden önce varlığımı bile bilmiyordum )

Öyleyse benim sorum şu, neden C # tasarımcıları casekendi yerel alanlarını engellemedi? Bunun tarihsel veya teknik bir sebebi var mı?


4
Sizin beyan actionönce değişkeni switchaçıklamada, yoksa kendi parantez içine her durumda koymak ve mantıklı davranış olacaktır.
Robert Harvey,

3
@RobertHarvey evet, daha da ileri gidebilir ve bunu hiç kullanmadan yazabilirim switch- Sadece bu tasarımın arkasındaki mantığı merak ediyorum.
pswg

c # her davadan sonra bir molaya ihtiyaç duyuyorsa, sanırım bu durum böyle değil!
tgkprog

@tgkprog Tarihsel bir nedenden dolayı olmadığına ve C # tasarımcılarının bunu, C # 'da unutmanın yaygın bir yanlışlığından emin olmamak için yaptıklarını düşünüyorum break.
svick

12
Bu ilgili SO sorusu üzerine Eric Lippert'in cevabına bakınız. stackoverflow.com/a/1076642/414076 Memnun kalmayabilirsiniz veya gelmeyebilirsiniz. "1999'da bunu yapmayı seçtikleri için seçtiler" şeklinde yazıyor.
Anthony Pegram

Yanıtlar:


24

Bence iyi bir sebep, diğer her durumda “normal” bir yerel değişkenin kapsamının parantez ( ) ile sınırlandırılmış bir blok olmasıdır{} . Normal olmayan yerel değişkenler, bir fordöngü değişkeni veya bildirilen bir değişken gibi, bir deyimden önce (genellikle bir bloktur) özel bir yapıda görünür using.

Bir başka istisna, LINQ sorgu ifadelerindeki yerel değişkenlerdir, ancak bunlar normal yerel değişken bildirimlerinden tamamen farklıdır, bu yüzden orada bir karışıklık olasılığı olduğunu düşünmüyorum.

Başvuru için, kurallar §3.7 C # spec kapsamlarındadır:

  • Yerel değişken bildirgesinde bildirilen yerel değişkenin kapsamı , bildirimin gerçekleştiği bloktur.

  • Bir deyimin anahtar bloğunda bildirilen yerel bir değişkenin kapsamı anahtar bloğudur .switch

  • Yerel bir değişkenin kapsamı bir ilan için-başlatıcısı a forifadesi olduğu için-başlatıcı , for-koşul , for-yineleyici ve içerdiği açıklamada ait forbeyanı.

  • Foreach ifadesinin bir parçası olarak bildirilen bir değişkenin kapsamı , using-ifade , lock-deyimi veya sorgu ifadesi , verilen yapının genişletilmesiyle belirlenir.

( switchBloğun neden açıkça belirtildiğinden tam olarak emin değilim , çünkü diğer tüm değişkenlerin aksine, yerel değişken bildirimleri için özel bir sözdizimi bulunmuyor.)


1
+1 Alıntıladığınız kapsamın linkini verebilir misiniz?
pswg

@pswg MSDN'de belirtilen özellikleri indirmek için bir bağlantı bulabilirsiniz . (Bu sayfada “Microsoft Geliştirici Ağı (MSDN)” yazan bağlantıyı tıklayın.)
svick,

Bu yüzden Java özelliklerine baktım ve switchesbu konuda aynı şekilde davranıyor gibi görünüyorum (sonunda atlama yapmayı gerektirmemek hariç case). Bu davranış basitçe oradan kopyalandı görünüyor. Yani kısa cevap - caseifadeler bloklar oluşturmaz, sadece bir switchbloğun bölümlerini tanımlarlar - ve bu nedenle kendi başlarına kapsamları yoktur.
pswg

3
Ynt: Anahtar bloğunun neden açıkça belirtildiğinden tam olarak emin değilim - teknik özelliklerin yazarı seçiciydi, bir anahtar bloğunun normal bir bloktan farklı bir gramer olduğuna işaret etti .
Eric Lippert

42

Ama öyle. Satırları sararak yerel kapsamlar oluşturabilirsiniz.{}

switch (criterion.ChangeAction)
{
  case BindingType.Inherited:
    {
      var action = (byte)ChangeAction.Inherit;
      return (x => x.Action == action);
    }
  case BindingType.ExplicitValue:
    {
      var action = (byte)ChangeAction.SetValue;
      return (x => x.Action == action);
    }
  default:
    // TODO: Localize errors
    throw new InvalidOperationException("Invalid criterion.");
}

Yup bunu kullandı ve açıklandığı gibi çalışıyor
Mvision

1
+1 Bu iyi bir püf noktası, ancak svick'in cevabını orijinal soruma cevap vermeye en yakın olduğu için kabul edeceğim .
pswg

10

Konuyla ilgili açık bir cevap olan Eric Lippert'i alıntılayacağım:

Makul bir soru "bu neden yasal değil?" Makul bir cevap "peki, neden olmalı"? İki yoldan birine sahip olabilirsin. Ya bu yasaldır:

switch(y) 
{ 
    case 1:  int x = 123; ...  break; 
    case 2:  int x = 456; ...  break; 
}

veya bu yasaldır:

switch(y) 
{
    case 1:  int x = 123; ... break; 
    case 2:  x = 456; ... break; 
}

ama ikisine de sahip olamazsın. C # tasarımcıları, bunu yapmanın daha doğal yolu gibi görünmek için ikinci yolu seçti.

Bu karar 7 Temmuz 1999'da yalnızca on yıl önce utangaç olarak alındı. O günden gelen notlardaki yorumlar oldukça kısa, "Bir anahtar kutusunun kendi bildirim alanını yaratmadığını" ve sonra neyin işe yarayıp neyin yaramayacağını gösteren bazı örnek kodlar verdiğini yazıyor.

Bu belirli günde tasarımcıların kafasında ne olduğu hakkında daha fazla bilgi edinmek için, on yıl önce ne düşündüklerini düşünmek için birçok insanı rahatsız etmek zorunda kaldım - ve nihayetinde önemsiz bir sorun olduğu konusunda onları rahatsız ettim; Bunu yapmayacağım.

Kısacası, bir yol ya da diğerini seçmek için özellikle zorlayıcı bir neden yoktur; İkisinin de yararları var. Dil tasarım ekibi bir yol seçti çünkü bir tane seçmek zorunda kaldılar; Seçtikleri kişi bana mantıklı geliyor.

Öyleyse, 1999'un C # geliştirici ekibine Eric Lippert'ten daha yakın olmadıkça, bunun nedenini asla bilemeyeceksiniz!


Aşağılayıcı değil, ama bu dün soru üzerine yapılan bir yorumdu .
Jesse C. Dilimleyici

4
Bunu görüyorum, ancak yorumcu bir cevap yayınlamadığından, bağlantının içeriğinden alıntı yaparak açıkça cevap vermenin iyi bir fikir olacağını düşündüm. Ve olumsuz kısımları umursamıyorum! OP, tarihi bir neden soruyor, "Nasıl yapılır" cevabını sormuyor.
Cyril Gandon,

5

Açıklama basittir - çünkü öyledir.

(Başka bir cevapta da belirtildiği gibi, C # geliştiricileri, vaka kapsamlarıyla ilgili bu kararın nasıl alındığına dair belgelere sahip değillerdir. Ancak C # sözdizimi için gösterilmemiş olan ilke, farklı nedenlerde bulunmaları için zorunlu nedenleri olmadıkça, Java'yı kopyaladıklarıdır. )

C de durum ifadeleri goto etiketlerine benzer. Switch deyimi hesaplanan bir goto için gerçekten daha güzel bir sözdizimidir . Vakalar, giriş noktalarını anahtar bloğuna tanımlar . Varsayılan olarak, açık bir çıkış olmadıkça, kodun geri kalanı yürütülür. Bu yüzden sadece aynı kapsamı kullandıkları mantıklı.

(. Daha da önemlisi Goto en yapılandırılmış değildir - bu tanımlamaz veya sınırlandırmaktadır bölümleri kod, sadece sıçrama noktalarını tanımlar Bu nedenle bir git etiketi. Edilemez bir kapsam tanıtmak.)

C #, sözdizimini korur, ancak her (boş olmayan) durum maddesinden sonra bir çıkış gerektirerek "düşmeye" karşı bir koruma sağlar. Ancak bu, anahtar hakkındaki düşüncemizi değiştiriyor! Davalar artık bir if-başka şubelerdeki gibi alternatif şubelerdir . Bu, her dalın tıpkı if cümleleri veya yineleme cümleleri gibi kendi kapsamını tanımlamasını bekleyeceğimiz anlamına gelir.

Kısacası: Davalar aynı kapsamı paylaşıyor, çünkü C'de böyle. Ancak C # 'da tuhaf ve tutarsız görünüyor çünkü davaları hedeflerden ziyade alternatif dallar olarak düşünüyoruz.


1
C # 'da tuhaf ve tutarsız görünüyor çünkü davaları hedeflerden ziyade alternatif dallar olarak düşünüyoruz. ” C1'de neden yanlış hissettiğinin arkasındaki estetik nedenleri açıklamak için +1 .
pswg

4

Kapsama bakmanın basitleştirilmiş bir yolu, kapsamı bloklarla dikkate almaktır {}.

Yana switchengellemeleri içermez, farklı kapsamları sahip olamaz.


3
Ne hakkında for (int i = 0; i < n; i++) Write(i); /* legal */ Write(i); /* illegal */? Blok yok, ancak farklı kapsamlar var.
svick

@svick: Dediğim gibi, basitleştirilmiş, forifade tarafından oluşturulan bir blok var . switchher durumda, sadece en üst düzeyde blok oluşturmaz. Benzerlik, her bir ifadenin bir blok oluşturmasıdır ( switchifade olarak sayılır ).
Guvante

1
Öyleyse neden casesaymadın?
svick,

1
@svick: caseİsteğe bağlı olarak eklemeye karar vermediğiniz sürece, çünkü bir blok içermiyor. forbir sonraki cümle için devam etmediğiniz sürece devam eder, {}böylece her zaman bir bloğu vardır, ancak bir caseşey gerçek bloğu, switchifadeyi bırakmanıza neden olana kadar sürer .
Guvante
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.