2010-10-04 33 views
13

, varsa bir struct şöyle: Programın çıktısı olacaktırC#: Okunur yapılardaki mutasyonlar neden kırılmaz? C#

static readonly Counter counter = new Counter(); 

static void Main() 
{ 
    // print the new value from the increment function 
    Console.WriteLine(counter.Increment()); 
    // print off the value stored in the item 
    Console.WriteLine(counter.Value); 
} 

:

struct Counter 
{ 
    private int _count; 

    public int Value 
    { 
     get { return _count; } 
    } 

    public int Increment() 
    { 
     return ++_count; 
    } 
} 

Ve bir program şöyle var

1 
0 

Bu tamamen yanlış görünüyor. Çıktının iki 1 olmasını beklerim (Counterclass ise ya da struct Counter : ICounter ve counter bir ICounter ise) ya da bir derleme hatası olabilir. Bunu derleme zamanında tespit etmenin oldukça zor bir konu olduğunu anlıyorum, fakat bu davranış mantığı ihlal ediyor gibi görünüyor.

Bu davranışın uygulama zorluğunun ötesinde bir nedeni var mı?

+10

Eric Lippert'in ["Mutant Readonly Structs"] başlıklı makalesine bakın. (Http://blogs.msdn.com/b/ericlippert/archive/2008/05/14/mutating-readonly-structs.aspx) –

+0

* Neden iki tane beklemek mi? Okumak istediğini söyledin, neden değişmesini istiyorsun? –

cevap

8

structs, değer türleridir ve bu nedenle bir değer türü sematiğine sahiptir. Bu, yapıya her eriştiğinizde temel olarak yapının değerinin bir kopyasıyla çalıştığınız anlamına gelir.

Örneğinizde, orijinal struct'u değiştirmezsiniz, ancak yalnızca geçici bir kopyasını.

daha fazla açıklama için bkz:

Why are mutable structs evil

.net
+4

Ancak "readonly" 'i çıkarırsanız, iki 1'lerin beklenen çıkışı olur. Dolayısıyla, bu örnekte açıkça bir kopya oluşturmuyor. Bu, readonly yapılar üzerinde çağrılan tüm işlevlerin, yapının kopyalarını oluşturduğu anlamına gelir (C#, bir mutasyon işlevinin hiçbir kavramına sahip olmadığı için)? –

+4

Evet, C# dil belirtiminin 7.5.4 bölümüne göre durum böyledir. Daha fazla ayrıntı almak için konuyla ilgili Eric Lippert'in gönderisine bakın. –

+1

. Net'deki talihsiz tasarım zafiyeti, yapı yöntemlerinin 'bunu' değiştirecek olup olmadıklarını gösterebilecekleri bir araç olmadığıdır. Eğer salt okunur yapılar üzerinde herhangi bir metot veya özellik kullanamazsa sinir bozucu olacağı için, “readonly” düzenleyicileri şereflendirilmediyse de can sıkıcı olurdu, C# ve vb.net yerine “uzlaşmak” salt okunur yapılar üzerinde yöntem çağırmalarının olması, yöntemi çağırmadan önce yapının bir kopyasını oluşturur. Yapı alanlarını doğrudan ortaya çıkarırsa, derleyicinin okuma erişimlerini yazımlardan ayırt edebildiğini ve ... – supercat

3

, bir yapı örneği metodu yapı bir ekstra ref parametresi olan bir statik yapı yöntemine anlama sahip olduğu yazın.

struct Blah { 
    public int value; 
    public void Add(int Amount) { value += Amount; } 
    public static void Add(ref Blah it; int Amount; it.value += Amount;} 
} 

yöntem çağırır:

someBlah.Add(5); 
Blah.Add(ref someBlah, 5); 

bir fark dışında anlama sahip: Bu durumda, bildirimleri verilen ikinci arama yalnızca someBlah kesilebilir bir depolama konumu ise, müsaade edilecek (değişken, alan, vb.) ve salt okunur bir depo yeri veya geçici bir değer (bir mülkün okunması sonucu vb.) değilse.

Bu, .net dilinin tasarımcılarıyla bir sorunla karşı karşıya kaldı: salt okunur yapılardaki herhangi bir üye işlevinin kullanımını devre dışı bırakmak can sıkıcı olurdu, ancak üye işlevlerinin salt okunur değişkenlere yazmasına izin vermek istemediler. "Punt" yapmaya karar verdiler ve bunu bir salt okunur yapıda bir örnek yöntemini çağırmak, yapının bir kopyasını oluşturacak, bunun üzerindeki işlevi çağıracak ve sonra da atacaklardır. Bu, altta yatan yapıyı yazmayan örnek yöntemlerine yapılan çağrıları yavaşlatmanın etkisine sahiptir ve bunu yapmak için, salt okunur bir yapıda temel yapıyı güncelleyen bir yöntemi kullanma denemesi, farklı kırık semantik'u Yapıyı doğrudan geçtiyse elde edilir. Kopyalamadan alınan ekstra sürenin, kopya olmadan doğru olmayacak olan durumlarda doğru semantiği hemen hemen hiç vermeyeceğini unutmayın.

.net'teki en büyük pecer'lerden biri hala (en az 4.0 ve muhtemelen 4.5'den itibaren) hala bir yapı üyesi işlevinin this değiştirip değiştirmediğini gösterebileceği bir öznitelik olmamasıdır.İnsanlar, yapıların mutasyona uğramış yöntemleri güvenli bir şekilde sunmalarına izin vermek için araçlar sağlamaktan ziyade, yapıların nasıl değişmez olması gerektiğine ilişkin olarak rağbet görürler. Bu, "immutable" yapıları denilen gerçeği yalan olmasına rağmen. Değişken depolama konumlarındaki tüm önemsiz olmayan değer türleri, tüm kutulu değer türleri gibi, değiştirilebilir. Bir "immutable" yapısını yapmak, yalnızca bir alanı değiştirmek istediğinde tüm yapıyı yeniden yazmayı zorunlu kılabilir, ancak struct1 = struct2 yapı1, tüm ortak ve özel alanlarını struct2'den kopyalayarak değiştirir ve struct için tür tanımında hiçbir şey yoktur Bunu önlemek için yapabilir (herhangi bir alanın olmaması hariç) yapı üyelerinin beklenmedik mutasyonlarını önlemek için hiçbir şey yapmaz. Dahası, iş parçacığı sorunları nedeniyle, yapılar kendi alanları arasında herhangi bir değişmez ilişkiyi yürütme yetenekleri bakımından çok sınırlıdır. IMHO, genel olarak, rastgele alan erişimine izin veren bir yapı için daha iyi olacaktır; bu da, bir yapıyı alan herhangi bir kodun, koşullara uymayan yapıların oluşumunu engellemeye çalışmaktan ziyade, alanlarının gerekli tüm koşulları karşılayıp karşılamadığını kontrol etmesi gerektiğini açıkça belirtmektedir.

+0

* '" struct1 = struct2' struct1 değiştiğinde struct1 "* - bu doğru, ancak bununla ilgili pratik problemler var mı (genelde atomik değil)? 'Struct1' ve' struct2' değişkenler ya da gerçek verilerle dolu alanlardır, bu nedenle kopyalama, bu durumda C ve C++ 'da varsayılan olarak nasıl yapılandırılacağı olduğundan, bu durumda ne beklenmesi gerektiğidir. Struct1' 'readonly' alanı değilse, bu durumda derleme zamanında başarısız olur. – Groo

+0

Her neyse, yardımcı bilgi için +1, ama ben sadece bir salt okunur yapıda bir örnek yöntemini çağırarak yapılacağını söylediğin parçayı biraz yeniden ifade edeceğim *, çünkü * okumaya * struct alanı bu yapının bir kopyasını oluşturur. Eğer değeri ilk önce yerel bir değişkene okursanız, bunu değiştirebilir (iç alanları değişebilir olduğu sürece) ve .NET yığın tabanlı yapı üzerinde doğrudan çalışır ("uygulama ayrıntıları", biliyorum). Bir alan "readonly" değilse, yöntemler kopyalamaya gerek kalmadan doğrudan sahada çalışabilir. – Groo

+0

@Groo: Yapı1 = struct2'nin tüm alanlarının üzerine yazarak * var olan * struct1'i mutasyona uğrattığını, ancak bu köşe durumlarını doğru bir şekilde anlayabilmenin tek yolunun ne olduğunu anlamasının önemli olduğu birçok köşe durumu yok. aslında oluyor. İkinci noktanızla ilgili olarak, önerdiğin şeyi tam olarak açıklayamıyorum; "salt okunur yapı" ifadesi, hem "salt okunur" niteleyiciye sahip değişkenlere hem de işlev veya özellik dönüş değerleri gibi geçici depolama konumlarına karşılık gelir; okuma/yazma özelliklerinin dönüş değerlerinin salt okunur olduğuna dikkat etmem gerekir mi? – supercat