Neden numaralandırma değerlerine 0.0 atayabilirim ama 1.0 atamıyorum


90

Sadece meraktan soruyorum: neden numaralandırma türündeki bir değişkene 0.0 atayabilirim ama 1.0 atayamıyorum? Aşağıdaki koda bir göz atın:

public enum Foo
{
    Bar,
    Baz
}

class Program
{
    static void Main()
    {
        Foo value1 = 0.0;
        Foo value2 = 1.0;   // This line does not compile
        Foo value3 = 4.2;   // This line does not compile
    }
}

Sayısal türler ve numaralandırma değerleri arasındaki dönüşümlere yalnızca yayınlar aracılığıyla izin verildiğini düşündüm. Yani Foo yazabilirim, value2 = (Foo) 1.0;böylece 2. satır Mainderlenebilir. 0.0C # 'daki değer için neden bir istisna var ?


17
Benim için o, garip olabilir özel enum çift değişmezi 0.0 atarsınız. Değil o olamaz atama 1.0değişmezi için özel numaralandırma.
Ilya Ivanov

2
Derleyicinin onu 0onun yerine ele aldığından şüpheleniyorum . Bir keresinde benzer bir sorum vardı ve Rawling burada harika bir cevap yayınladı .
Dostane


Yanıtlar:


98

0.0'ı kullanabileceğiniz bir hatadır. Derleyici, sıfır değerine sahip tüm sabit ifadeleri örtük olarak sadece 0 olarak ele alır.

Şimdi, derleyicinin C # 5 belirtiminin 6.1.3 bölümüne göre sabit bir 0 ifadesinden sizin numaranıza örtük bir dönüşüme izin vermesi doğrudurint :

Örtülü bir numaralandırma dönüşümü, ondalık tamsayı-değişmez 0'ın herhangi bir enum türüne ve temel alınan türü bir enum türü olan herhangi bir null yapılabilir türe dönüştürülmesine izin verir. İkinci durumda, dönüştürme, temeldeki enum türüne dönüştürülerek ve sonucu sararak değerlendirilir (§4.1.10).

C # ekibiyle daha önce bunun hakkında konuştum: Yanlışlıkla 0,0 (ve aslında 0,0 m ve 0,0f) değerlerinden enum değerlerine dönüştürmeyi kaldırmayı çok isterdi , ancak ne yazık ki çok fazla kod kırdığını anladım - ilk etapta asla izin verilmemeliydi.

Mono mcsderleyici , aşağıdakilere izin vermesine rağmen, tüm bu kayan nokta dönüşümlerini yasaklar :

const int Zero = 0;
...

SomeEnum x = Zero;

gerçeğine rağmen Zerosabit ifadesidir ama değil ondalık tamsayı .

Gelecekte 0 değerine sahip herhangi bir tamsayı sabit ifadesine izin vermek için (yani taklit etmek için mcs) C # spesifikasyonunun değiştiğini görmek beni şaşırtmaz , ancak kayan nokta dönüşümlerinin resmi olarak doğru olmasını beklemem . (Daha önce C # 'ın geleceğini tahmin etme konusunda yanılmışım elbette ...)


3
Spec Başına, sadece olması gerekiyordu literal o reddetmesi gerektiğini Yani 0'a 1-1sabit - intifadesini değeriyle 0. Ama gözlemlediğiniz gibi, derleyici buradaki şartnameye uygun değil.
Damien_The_Unbeliever

4
it broke too much code- böyle bir kod yazmak için herhangi bir neden hayal etmek gerçekten zor.
Ilya Ivanov

1
@ObsidianPhoenix: Ne demek istediğinizden emin değilim. Bu tam olarak eşdeğer: SomeEnum x = (SomeEnum) 0;. Bu, adlandırılmış bir sıfır değeri olup olmadığıdır.
Jon Skeet

2
@ObsidianPhoenix: Şey, hayır, çünkü değeri Test.Foo1, 0 değil ... yine, bu sizin yazdığınız gibi tamamen aynı Test v1 = (Test) 0;- ve bu davranış , numaralandırmada adlandırılmış bir değer olmayan herhangi bir değer için geçerli .
Jon Skeet

2
@JonSkeet, Roslyn'de düzeltilecek mi?
Maksimum

98

Jon'un cevabı doğru. Buna şu noktaları eklerim.

  • Bu aptal ve utanç verici hataya ben sebep oldum. Birçok özür.

  • Hata, derleyicideki "ifade sıfırdır" koşulunun anlamını yanlış anlamamdan kaynaklandı; Sadece tamsayı sıfır eşitliğini kontrol ettiğine inandım, aslında "bu, bu türün varsayılan değeri mi?" Aslında, hatanın önceki bir sürümünde, herhangi bir türün varsayılan değerini bir numaralandırmaya atamak gerçekten mümkündü! Artık sayıların yalnızca varsayılan değerleridir. (Ders: Yardımcı yüklemlerinizi dikkatlice adlandırın.)

  • Karıştırdığım uygulamaya çalıştığım davranış, aslında biraz farklı bir hata için geçici bir çözümdü. Korkunç hikayenin tamamını buradan okuyabilirsiniz: https://docs.microsoft.com/en-us/archive/blogs/ericlippert/the-root-of-all-evil-part-one ve https://docs.microsoft .com / en-us / archive / blogs / ericlippert / the-root-of-all-evil-part-two (Ders: Eski hataları düzeltirken yeni ve daha kötü hataları ortaya çıkarmak çok kolaydır.)

  • C # ekibi bu hatalı davranışı düzeltmek yerine korumaya karar verdi çünkü mevcut kodu zorlayıcı bir fayda sağlamadan kırma riski çok yüksekti. (Ders: ilk seferde doğru yapın!)

  • Ben bu davranışı korumak için Roslyn yazdığı kod yönteminde bulunabilir IsConstantNumericZeroiçinde https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs - Roslyn davranışının tam olarak ne olduğu hakkında daha fazla ayrıntı için ona bakın. Kodun neredeyse tamamını Dönüşümler dizinine yazdım; C # 'nin yorumlarda belirtimden nasıl ayrıldığına dair pek çok ilginç gerçek olduğu için hepsini okumanızı tavsiye ederim. Bulmayı kolaylaştırmak için her birini SPEC İHLALİ ile süsledim.

İlgi çekici bir nokta daha: C # ayrıca herhangi bir enum değerinin sıfırlığına bakılmaksızın bir numaralandırma başlatıcısında kullanılmasına izin verir :

enum E { A = 1 }
enum F { B = E.A }  // ???

Spesifikasyon, bunun yasal olup olmadığı konusunda biraz belirsizdir, ancak yine, bu derleyicide uzun süredir olduğu için, yeni derleyicilerin davranışı sürdürmesi muhtemeldir.


10
Bu gerçekten harika, sonunda yazdığınız kodu görebiliyorum. Roslyn kaynak kodunun açık kaynak olması harika. Şimdi, değişiklik geçmişi sağlamamak için geçerli nedenlerin (teknik / yasal) olduğunu tamamen anlıyorum, ancak kodun nasıl geliştiğini görmek için değişiklik geçmişini görmek çok harika olurdu.
SolutionYogi

The C# team decided to enshrine this buggy behaviour rather than fixing it because the risk of breaking existing code for no compelling benefit was too high.Bu davranışa güvenen pek çok insan olacağını sanmıyorum ve düzeltilmesi daha iyi olabilecek bu tuhaflıklardan biri. Ancak, çok da zarar vermez (spesifikasyonu uygulayan projeler hariç).
Aidiakapi

5
@Aidiakapi: Gerçekten de etkilenen insan sayısı az olmalı; sıfır değil. C # ekibi, değişiklikleri çok ciddiye alır. Düzeltmeyi yapmanın daha iyi olduğunu söylemek sizin için kolaydır ; Hiçbir fayda sağlamayan önemsiz değişikliğinizin sistem entegrasyonlarını bir gün geciktirdiğinden şikayet etmek için başkan yardımcınızı arayan öfkeli müşterilerle uğraşmak zorunda değilsiniz.
Eric Lippert

3
Daha da kötüleşiyor. Bu tür tüm önemli değişiklikler (ideal olarak) Microsoft'un Çerçeve geçiş kılavuzunda listelenecektir. Bu liste ne kadar uzun olursa, kullanıcılar uygulamalarını taşımada o kadar tereddütlü olur. Bu nedenle, küçük bir kırılma değişikliği bile şunlara neden olur: 1. Az sayıda uygulama bozulabilir. 2. Yükseltmeyi reddedecek az sayıda kullanıcı (sorun onları etkilemese bile). 3. Az sayıda kullanıcı, son değişikliğin onları etkileyip etkilemediğini değerlendirerek kaynakları israf etmek. 4. # 1, # 2 ve # 3'ten herkese şikayet etmek için kullanıcılar.
Brian

@EricLippert "Spesifikasyon biraz belirsiz" ise, spesifikasyonu güncellemek mantıklı olmaz mı? (Gerçek soru!)
James

10

C # 'da numaralandırmalar tanım gereği integral değerleridir. Tutarlılık için C # bu atamalardan hiçbirini kabul etmemelidir, ancak0.0 sessizce integral olarak ele alınır 0. Bu muhtemelen, değişmezin 0özel olarak ele alındığı ve esasen herhangi bir türü alabileceği C'den bir tutmadır - tamsayı, kayan nokta sayısı, boş gösterici… adını siz koyun.


3
soru neden ? Eğer giderseniz IL- tamsayı değerini yığına itiyorIL_0001: ldc.i4.0
Ilya Ivanov

@IlyaIvanov Güncellemeye bakın. Ama dürüst olmak gerekirse, cevap "iyi bir sebep yok".
Konrad Rudolph

2
Bence bu, C # özelliklerine bakarsanız yasal olmadığı durumlardan biri , ancak MS'nin ürettiği herhangi bir C # derleyicisine bakarsanız, bunu yapar.
Damien_The_Unbeliever

3

enum gerçekten (onu destekleyen tüm dillerde) sayısal değerler yerine anlamlı ve benzersiz dizelerle (etiketler) çalışmanın bir yolu olarak tasarlanmıştır. Dolayısıyla, örneğinizde, Foo numaralandırılmış bir veri türü ile uğraşırken yalnızca Bar ve Baz kullanmalısınız . Pek çok derleyici ondan kurtulmanıza izin verse bile, asla bir tamsayı kullanmamalısınız (karşılaştırmamalı veya atamamalısınız) (numaralandırmalar genellikle dahili tamsayıdır) ve bu durumda 0,0, dikkatsizce derleyici tarafından 0 olarak değerlendirilir.

Kavramsal olarak, numaralandırılmış bir değere bir n tamsayısı eklemek , satırın aşağısında n değerleri elde etmek veya ne kadar uzakta olduklarını görmek için val2 - val1 almak doğru olmalıdır, ancak dil spesifikasyonu buna açıkça izin vermedikçe, I Bundan kaçınırdım. (Numaralandırılmış bir değeri, onu kullanabileceğiniz şekillerde bir C işaretçisi gibi düşünün.) Numaralandırmaların kayan nokta sayıları ve aralarında sabit bir artışla gerçekleştirilememesinin bir nedeni yok, ancak duymadım bu herhangi bir dilde yapılıyor.


C # 'da numaralandırmaları bu şekilde kullanmamam gerektiğini biliyorum - ama bu zeka oyununu buldum ve 0.0'ın neden işe yaradığını öğrenmek istedim, ancak 1.0 değil. Bunun C # derleyicisiyle bir şey olması gerektiğini biliyordum, çünkü IL Kodunun Foo v1 = 0.0;ile aynı olduğunu görebilirsiniz Foo v2 = Foo.Bar.
feO2x
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.