Atama ifadeleri neden bir değer döndürür?


126

Buna izin verilir:

int a, b, c;
a = b = c = 16;

string s = null;
while ((s = "Hello") != null) ;

Anladığım kadarıyla, atama s = ”Hello”;yalnızca “Hello”atanmaya neden olmalı s, ancak işlem herhangi bir değer döndürmemelidir. Eğer bu doğru ((s = "Hello") != null)olsaydı, nullhiçbir şeyle kıyaslanamayacağı için bir hata üretirdi.

Atama ifadelerinin bir değer döndürmesine izin vermenin ardındaki mantık nedir?


44
Referans için, bu davranış C # spesifikasyonunda tanımlanmıştır : "Basit bir atama ifadesinin sonucu, soldaki işlenene atanan değerdir. Sonuç, sol işlenenle aynı türe sahiptir ve her zaman bir değer olarak sınıflandırılır."
Michael Petrotta

1
@Skurmedel Olumsuz oy veren ben değilim, sana katılıyorum. Ama belki retorik bir soru olduğu düşünüldüğü için?
jsmith

Pascal / delphi atamasında: = hiçbir şey döndürmez. nefret ettim.
Andrey

20
C dalındaki diller için standart. Atamalar ifadelerdir.
Hans Passant

"atama ... yalnızca" Merhaba "nın e-postalara atanmasına neden olmalıdır, ancak işlem herhangi bir değer döndürmemelidir" - Neden? "Atama ifadelerinin bir değer döndürmesine izin vermenin ardındaki mantık nedir?" - Kendi örneklerinizin de gösterdiği gibi, faydalı olduğu için. (İkinci örneğiniz aptalca, ama while ((s = GetWord()) != null) Process(s);değil).
Jim Balter

Yanıtlar:


160

Anladığım kadarıyla, atama s = "Merhaba"; e-postalara yalnızca "Merhaba" atanmasına neden olmalı, ancak işlem herhangi bir değer döndürmemelidir.

Anlayışınız% 100 yanlış. Bu yanlış şeye neden inandığınızı açıklayabilir misiniz?

Atama ifadelerinin bir değer döndürmesine izin vermenin ardındaki mantık nedir?

Öncelikle, atama ifadeleri bir değer üretmez. Atama ifadeleri bir değer üretir. Bir atama ifadesi yasal bir beyandır; C # 'da yasal ifadeler olan yalnızca bir avuç ifade vardır: bir ifadenin beklenmesi, örnek oluşturma, artırma, azaltma, çağırma ve atama ifadeleri bir ifadenin beklendiği yerde kullanılabilir.

C # 'da bir tür değer üretmeyen tek bir ifade türü vardır, yani geri dönen boşluk olarak yazılan bir şeyin çağrılması. (Ya da eşdeğer olarak, ilişkili sonuç değeri olmayan bir görevin beklenmesi.) Diğer her ifade türü, bir değer ya da değişken ya da başvuru ya da özellik erişimi ya da olay erişimi üretir ve bu böyle devam eder.

Beyan olarak yasal olan tüm ifadelerin yan etkileri için yararlı olduğuna dikkat edin . Buradaki temel fikir budur ve sanırım sezginizin nedeni, atamaların ifadeler değil ifadeler olması gerektiğidir. İdeal olarak, her ifade için tam olarak bir yan etkimiz olur ve bir ifadede hiçbir yan etkimiz olmaz. Bir yan etki kodu hiç bir ekspresyon bağlamında kullanılabileceğini biraz garip.

Bu özelliğe izin vermenin ardındaki mantık, (1) sıklıkla uygun olması ve (2) C benzeri dillerde deyimsel olmasıdır.

Sorunun sorulduğu not edilebilir: Bu neden C benzeri dillerde deyimseldir?

Ne yazık ki Dennis Ritchie artık sormaya müsait değil, ancak benim tahminime göre bir görev neredeyse her zaman bir sicilde atanmış olan değeri geride bırakıyor . C çok "makineye yakın" bir dil türüdür. Akla yatkın görünüyor ve C'nin tasarımına uygun olarak, temelde "az önce atadığım değeri kullanmaya devam et" anlamına gelen bir dil özelliği var. Bu özellik için bir kod üreteci yazmak çok kolaydır; atanan değeri depolayan kaydı kullanmaya devam edersiniz.


1
Bir değer döndüren herhangi bir şeyin farkında değildi (örnek oluşturma bile bir ifade olarak kabul edilir)
user437291

9
@ user437291: Örnek oluşturma bir ifade değilse, inşa edilen nesne ile hiçbir şey yapamazsınız - onu bir şeye atayamazsınız, onu bir yönteme geçiremezsiniz ve herhangi bir yöntemi çağıramazsınız üstünde. Oldukça yararsız olur, değil mi?
Timwi

1
Bir değişkeni değiştirmek aslında atama operatörünün bir yan etkisidir ve bir ifade oluşturarak birincil amacı olarak bir değer verir.
mk12

2
"Anlayışınız% 100 yanlış." - OP'nin İngilizce kullanımı en iyisi olmasa da, "anlayışı" ne olması gerektiğiyle ilgilidir, onu bir fikir haline getirir, bu yüzden "% 100 yanlış" olabilecek bir şey değildir. . Bazı dil tasarımcıları, OP'nin "olması gerektiği" konusunda hemfikirdir ve atamaların hiçbir değeri yoktur veya alt ifadeler olmalarını başka şekilde yasaklar. Sanırım bu, =/ ==typo için bir aşırı tepki =, parantezli olmadıkça değerinin kullanılmasına izin verilmeyerek kolayca ele alınabilir. örn., if ((x = y))veya if ((x = y) == true)izin verilir, ancak if (x = y)değildir.
Jim Balter

"Bir değer döndüren herhangi bir şeyin ... bir ifade olarak kabul edildiğinin farkında değildim" - Hmm ... bir değer döndüren her şey bir ifade olarak kabul edilir - ikisi neredeyse eşanlamlıdır.
Jim Balter

44

Cevabı vermedin mi? Tam olarak bahsettiğiniz yapı türlerini etkinleştirmek için.

Atama operatörünün bu özelliğinin kullanıldığı yaygın bir durum, satırları bir dosyadan okumaktır ...

string line;
while ((line = streamReader.ReadLine()) != null)
    // ...

1
+1 Bu, bu yapının en pratik kullanımlarından biri olmalı
Mike Burton

11
Bunu gerçekten basit özellik başlatıcıları için gerçekten seviyorum, ancak dikkatli olmalısınız ve okunabilirlik için düşük "basit" çizginizi çizmelisiniz: return _myBackingField ?? (_myBackingField = CalculateBackingField());Boşlukları kontrol etmekten ve atamadan çok daha az kaygı.
Tom Mayfield

35

Atama ifadelerinin en sevdiğim kullanımı, tembel olarak başlatılan özellikler içindir.

private string _name;
public string Name
{
    get { return _name ?? (_name = ExpensiveNameGeneratorMethod()); }
}

5
Bu yüzden bu kadar çok insan ??=sözdizimini önerdi .
konfigüratör

27

Birincisi, sizin örneğinizde olduğu gibi görevlerinizi zincirlemenize izin verir:

a = b = c = 16;

Bir diğeri için, bir sonucu tek bir ifadede atamanıza ve kontrol etmenize olanak tanır:

while ((s = foo.getSomeString()) != null) { /* ... */ }

Her ikisi de muhtemelen şüpheli nedenlerdir, ancak kesinlikle bu yapıları seven insanlar var.


5
+1 Zincirleme kritik bir özellik değil, ancak pek çok şey olmadığında "sadece işe yaradığını" görmek güzel.
Mike Burton

Ayrıca zincirleme için +1; Bunu her zaman "ifadelerin dönüş değerleri" nin doğal yan etkisinden ziyade dil tarafından ele alınan özel bir durum olarak düşünmüşümdür.
Caleb Bell

2
Ayrıca return (HttpContext.Current.Items["x"] = myvar);
Serge Shultz

14

Daha önce bahsedilen nedenlerin yanı sıra (atama zinciri, while döngüleri içinde ayarla ve test et, vb.), İfadeyi doğru şekilde kullanmak için usingbu özelliğe ihtiyacınız vardır:

using (Font font3 = new Font("Arial", 10.0f))
{
    // Use font3.
}

MSDN, kullanıldıktan sonra atıldıktan sonra bile kapsamda kalacağından, tek kullanımlık nesneyi kullanma ifadesi dışında bildirmekten vazgeçirir (bağladığım MSDN makalesine bakın).


4
Bunun gerçekten bir görev zinciri olduğunu sanmıyorum.
kenny

1
@kenny: Uh ... Hayır, ama ben de asla iddia etmedim. Dediğim gibi, atama zincirleme de dahil olmak üzere daha önce bahsedilen nedenlerin yanı sıra , atama operatörünün atama işleminin sonucunu döndürmediği gerçeği olmasaydı, using ifadesini kullanmak çok daha zor olurdu. Bu, atama zincirleme ile gerçekten ilgili değil.
Martin Törnwall

1
Ancak Eric'in yukarıda belirttiği gibi "atama ifadeleri bir değer döndürmez". Bu aslında using ifadesinin dil sözdizimidir, bunun kanıtı, bir using ifadesinde "atama ifadesi" kullanılamayacağının kanıtıdır.
kenny

@kenny: Özür dilerim, terminolojim açıkça yanlıştı. Using ifadesinin, dilin bir değer döndüren atama ifadeleri için genel desteğe sahip olmadan uygulanabileceğinin farkındayım. Benim gözümde bu son derece tutarsız olur.
Martin Törnwall

2
@kenny: Aslında, tek bir tanım-ve-atama deyimi, ya kullanabilir veya herhangi bir ifadeyi using. Bunların hepsi yasal şunlardır: using (X x = new X()), using (x = new X()), using (x). Bununla birlikte, bu örnekte, using ifadesinin içeriği, atamanın bir değer döndürmesine hiç dayanmayan özel bir sözdizimidir - Font font3 = new Font("Arial", 10.0f)bir ifade değildir ve ifadeleri bekleyen hiçbir yerde geçerli değildir.
konfigüratör

10

Eric Lippert'in cevabında belirttiği belirli bir noktayı ayrıntılandırmak ve kimsenin hiç değinmediği belirli bir olaya dikkat çekmek istiyorum. Eric dedi:

[...] bir atama hemen hemen her zaman bir kayıt defterinde atanmış olan değeri geride bırakır.

Atamanın her zaman sol operandımıza atamaya çalıştığımız değeri geride bırakacağını söylemek isterim. Sadece "neredeyse her zaman" değil. Ancak bilmiyorum çünkü bu konuyu belgelerde yorumlanmış olarak bulamadım. Teorik olarak "geride bırakmak" ve sol işleneni yeniden değerlendirmek için çok etkili uygulanan bir prosedür olabilir, ancak verimli mi?

Bu konudaki yanıtlarda şimdiye kadar oluşturulmuş tüm örnekler için 'Etkili' evet. Ancak get ve set erişimcilerini kullanan özellikler ve dizinleyiciler söz konusu olduğunda verimli mi? Bir şey değil. Bu kodu düşünün:

class Test
{
    public bool MyProperty { get { return true; } set { ; } }
}

Burada, özel bir değişken için sarmalayıcı bile olmayan bir özelliğimiz var. Ne zaman çağrılsa, yerine geri dönecek, değerini belirlemeye çalıştığı zaman hiçbir şey yapmayacak. Dolayısıyla, bu mülk değerlendirildiği her zaman, doğru olacaktır. Bakalım ne olacak:

Test test = new Test();

if ((test.MyProperty = false) == true)
    Console.WriteLine("Please print this text.");

else
    Console.WriteLine("Unexpected!!");

Tahmin et ne basar? Yazdırır Unexpected!!. Görünüşe göre, set erişimcisi gerçekten çağrılıyor ve bu hiçbir şey yapmıyor. Ancak bundan sonra, erişimci hiçbir zaman çağrılmaz. Görev, mülkümüze atamaya falseçalıştığımız değeri geride bırakıyor . Ve bu falsedeğer, if ifadesinin değerlendirdiği şeydir.

Bu konuyu araştırmamı sağlayan gerçek dünya örneğiyle bitireceğim . List<string>Bir sınıfımın özel değişken olarak sahip olduğu bir koleksiyon ( ) için uygun bir sarmalayıcı olan bir indeksleyici yaptım .

Dizin oluşturucuya gönderilen parametre, koleksiyonumda bir değer olarak ele alınacak bir dizeydi. Get erişimcisi, bu değer listede mevcut olsa da olmasa da doğru veya yanlış döndürür. Böylece get erişimcisi, List<T>.Containsyöntemi kullanmanın başka bir yoluydu .

Dizinleyicinin set erişimcisi, argüman olarak bir dizeyle çağrıldıysa ve doğru işlenen bir bool ise true, o parametreyi listeye eklerdi. Ancak erişimciye aynı parametre gönderilmişse ve doğru işlenen bir bool ise false, onun yerine öğeyi listeden silerdi. Böylece set erişimcisi, hem List<T>.Addve hem de için uygun bir alternatif olarak kullanıldı List<T>.Remove.

Ağ geçidi olarak uygulanan kendi mantığımla listeyi saran düzgün ve kompakt bir "API" ye sahip olduğumu düşündüm. Yalnızca bir indeksleyicinin yardımıyla, birkaç tuş vuruşuyla birçok şey yapabilirim. Örneğin, listeme nasıl bir değer eklemeyi deneyebilirim ve onun içinde olduğunu nasıl doğrulayabilirim? Bunun gerekli olan tek kod satırı olduğunu sanıyordum:

if (myObject["stringValue"] = true)
    ; // Set operation succeeded..!

Ancak daha önceki örneğimin gösterdiği gibi, değerin gerçekten listede olup olmadığını görmesi gereken get erişimcisi çağrılmadı bile. trueDeğeri her zaman arkasında etkili şekilde get erişimcisine uygulanan etmişti ne olursa olsun mantığı yok bırakıldı.


Çok ilginç cevap. Ve test odaklı geliştirme hakkında olumlu düşünmemi sağladı.
Elliot

1
Bu ilginç olsa da, OP'nin sorusuna bir cevap değil, Eric'in cevabı üzerine bir yorum.
Jim Balter

Denenen bir atama ifadesinin ilk önce hedef özelliğin değerini almasını beklemem. Değişken değişebilirse ve türler eşleşiyorsa, eski değerin ne olduğu neden önemli olsun? Sadece yeni değeri ayarlayın. Yine de IF testlerinde görevlendirme hayranı değilim. Atama başarılı olduğu için doğru mu dönüyor, yoksa bir değer, örneğin doğruya zorlanan pozitif bir tamsayı veya bir boole atadığınız için mi? Uygulamaya bakılmaksızın, mantık belirsizdir.
Davos

6

Atama bir değer döndürmediyse, satır a = b = c = 16da çalışmayacaktır.

Ayrıca böyle şeyler yazabilmek while ((s = readLine()) != null)de bazen faydalı olabilir.

Öyleyse, atamanın atanan değeri döndürmesine izin vermenin ardındaki neden, bunları yapmanıza izin vermektir.


Kimse dezavantajlardan bahsetmedi - Buraya atamaların neden boş dönemediğini merak ettim, çünkü ortak atama ve karşılaştırma hatası 'if (birşey = 1) {}' her zaman doğru olarak dönmek yerine derlenemezdi. Şimdi bunun başka sorunlar yaratacağını görüyorum!
Jon

1
@Jon bu hata, =parantezli olmayan bir ifadede meydana gelmesine izin verilmeyerek veya uyarılarak kolayca önlenebilir (if / while ifadesinin parantezlerini saymaz). gcc, bu tür uyarılar verir ve bu nedenle, kendisiyle birlikte derlenen C / C ++ programlarından bu hata sınıfını ortadan kaldırmıştır. Diğer derleyici yazarlarının buna ve gcc'deki diğer birkaç iyi fikre bu kadar az ilgi göstermesi utanç verici.
Jim Balter

4

Ayrıştırıcının bu sözdizimini nasıl yorumlayacağını yanlış anladığınızı düşünüyorum. Atama değerlendirilecek ilk ve sonuç daha sonra NULL karşılaştırılacak, yani deyim eşdeğerdir:

s = "Hello"; //s now contains the value "Hello"
(s != null) //returns true.

Diğerlerinin de belirttiği gibi, bir atamanın sonucu, atanan değerdir. Sahip olmanın avantajını hayal etmekte zorlanıyorum

((s = "Hello") != null)

ve

s = "Hello";
s != null;

eşdeğer değil ...


İki satırınızın eşdeğer olduğuna dair pratik anlamda haklıyken, atamanın bir değer döndürmediğine dair ifadeniz teknik olarak doğru değildir. Anladığım kadarıyla, bir atamanın sonucunun bir değer olduğu (C # spesifikasyonuna göre) ve bu anlamda, 1 + 2 + 3
Steve Ellinger

3

Bence ana neden C ++ ve C ile (kasıtlı) benzerliktir. Atama operatörünün (ve diğer birçok dil yapısının) C ++ muadilleri gibi davranmasını sağlamak, en az sürpriz ilkesini izler ve herhangi bir programcı başka bir kıvrımdan gelir. parantez dili fazla düşünmeden bunları kullanabilir. C ++ programcıları için öğrenmesi kolay olmak, C # için ana tasarım hedeflerinden biriydi.


2

Gönderinize dahil ettiğiniz iki nedenden dolayı
1) böylece yapabilirsiniz a = b = c = 16
2) bir ödevin başarılı olup olmadığını test edebilirsiniz if ((s = openSomeHandle()) != null)


1

'A ++' veya 'printf ("foo")' ifadesinin kendi kendine yeten bir ifade olarak veya daha büyük bir ifadenin parçası olarak yararlı olabileceği gerçeği, C'nin ifade sonuçlarının olabileceği veya olmayabileceği olasılığına izin vermesi gerektiği anlamına gelir. Kullanılmış. Buna göre, yararlı bir şekilde bir değeri 'döndüren' ifadelerin de bunu yapabileceği genel bir görüş vardır. Atama zincirleme, C'de biraz "ilginç" olabilir ve söz konusu tüm değişkenler tam olarak aynı türe sahip değilse, C ++ için daha da ilginç olabilir. Bu tür kullanımlardan muhtemelen kaçınılması en iyisidir.


1

Başka bir harika örnek kullanım örneği, bunu her zaman kullanıyorum:

var x = _myVariable ?? (_myVariable = GetVariable());
//for example: when used inside a loop, "GetVariable" will be called only once

0

Buradaki cevaplarda görmediğim ekstra bir avantaj, atama için sözdiziminin aritmetiğe dayalı olmasıdır.

Şimdi x = y = b = c = 2 + 3aritmetikte C tarzı bir dilden farklı bir şey anlamına geliyor; aritmetik onun bir iddianın içinde, biz devlet o x o bir talimat var y vb ve C tarzı dilinde eşittir markaları yürütüldüğünde sonra x y vb eşittir.

Bununla birlikte, aritmetik ve kod arasında, iyi bir neden olmadıkça aritmetikte doğal olana izin vermemenin bir anlam ifade etmeyecek kadar yeterli ilişki olduğunu söyledi. (C-stili dillerin eşittir sembolünün kullanımından aldığı diğer bir şey de == eşitlik karşılaştırması için kullanılmasıdır. Burada yine de en sağdaki == bir değer döndürdüğü için bu tür zincirleme imkansız olacaktır.)


@JimBalter Merak ediyorum, beş yıl içinde, gerçekten karşı bir tartışma yapıp yapamayacağınızı.
Jon Hanna

@JimBalter hayır, ikinci yorumun aslında bir karşı argüman ve sağlam bir yorum. Benim düşünceme cevabım, ilk yorumun olmamasıydı. Aritmetik öğrenirken Yine de öğrenmek a = b = cdemektir bu ave bve caynı şeydir. C tarzı dil öğrenirken biz bundan sonra öğrenirler a = b = c, ave baynı şeydir c. Orada cevabım kendisi söylediği gibi semantik bir fark, kuşkusuz, ama yine bir çocuk gibi ben ilk kullanımını yaptığım bir dille programa öğrenince =atama için ama izin vermedi a = b = cgerçi bu bana mantıksız görünüyordu ...
Jon Hanna

… Kaçınılmaz çünkü bu dil aynı zamanda =eşitlik karşılaştırması için de kullanılıyordu , bu yüzden bu , C tarzı dillerde a = b = cne a = b == canlama geldiği anlamına gelmelidir. C'de izin verilen zincirlemeyi çok daha sezgisel buldum çünkü aritmetiğe bir benzetme yapabilirdim.
Jon Hanna

0

Bir grup şeyi güncellemem ve herhangi bir değişiklik olup olmadığına bakmam gerektiğinde atama dönüş değerini kullanmayı seviyorum:

bool hasChanged = false;

hasChanged |= thing.Property != (thing.Property = "Value");
hasChanged |= thing.AnotherProperty != (thing.AnotherProperty = 42);
hasChanged |= thing.OneMore != (thing.OneMore = "They get it");

return hasChanged;

Yine de dikkatli olun. Bunu kısaltabileceğinizi düşünebilirsiniz:

return thing.Property != (thing.Property = "Value") ||
    thing.AnotherProperty != (thing.AnotherProperty = 42) ||
    thing.OneMore != (thing.OneMore = "They get it");

Ancak bu aslında ilk doğruyu bulduktan sonra veya ifadelerini değerlendirmeyi bırakacaktır. Bu durumda bu, farklı olan ilk değeri atadığında sonraki değerleri atamayı durdurduğu anlamına gelir.

Bununla oynamak için https://dotnetfiddle.net/e05Rh8 adresine bakın

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.