Soru C # cinsindeki dize değişmezliği


StringBuilder sınıfının dahili olarak nasıl uygulandığını merak ettim, bu yüzden Mono'nun kaynak kodunu incelemeye karar verdim ve bunu Reflector'un Microsoft'un uygulamasının demonte koduyla karşılaştırmaya karar verdim. Esasen, Microsoft'un uygulama kullanıyor char[] dize temsilini dahili olarak saklamak ve bunu işlemek için bir sürü güvenli olmayan yöntem. Bu basit ve herhangi bir soru sormadı. Ama Mono'nun StringBuilder içinde bir dizgi kullandığını fark ettiğimde kafam karışmıştı:

private int _length;
private string _str;

İlk düşünce şuydu: "Ne anlamsız StringBuilder". Ama sonra işaretçiyi kullanarak bir dizgeyi değiştirmenin mümkün olduğunu anladım:

public StringBuilder Append (string value) 
{
     // ...
     String.CharCopy (_str, _length, value, 0, value.Length);
}

internal static unsafe void CharCopy (char *dest, char *src, int count) 
{
    // ...
    ((short*)dest) [0] = ((short*)src) [0]; dest++; src++;
}    

C / C ++ 'da biraz program yapardım, bu yüzden bu kodun bana çok fazla kafa karıştırdığını söyleyemem ama dizelerin tamamen değişmez olduğunu düşünmüştüm (yani bunu değiştirmenin kesinlikle bir yolu yok). Yani asıl sorular:

  • Tamamen değişmez bir tip oluşturabilir miyim?
  • Bu tür kodları performans kaygıları dışında kullanmak için herhangi bir sebep var mı? (değişmez türleri değiştirmek için güvenli olmayan kod)
  • Dizeler daha sonra içten diş güvenli midir veya değil mi?

25
2017-08-28 14:27


Menşei


Kendimi bir süre önce keşfettim ve onunla ilgili bir blog yazısı yazdığımdan çok etkilendim. Buraya dahil edeceğim sadece bu soruya dayanarak, onu ilginç bulabileceğinizi düşünüyorum. - Dan Tao


Cevaplar:


Tamamen değişmez bir tip oluşturabilir miyim?

CLR'nin değişmezliği zorladığı bir tür yaratabilirsiniz. Daha sonra "güvensiz" özelliğini kullanabilirsiniz. CLR zorlama mekanizmalarını kapatmak. Bu yüzden "güvensiz", "güvensiz" olarak adlandırılır - çünkü güvenlik sistemini kapatır. Güvenli olmayan kodda, işlemdeki belleğin her bir byte'ı, yeterince sıkı çalışırsanız yazılabilir olabilir, değişmez baytları ve değişmezliği zorlayan CLR'deki kodu içeren.

Değişmezliği kırmak için Yansıma'yı da kullanabilirsiniz. Yansıma ve güvensiz kodların ikisi de verilecek son derece yüksek bir güven seviyesi gerektirir.

Bu tür kodları performans kaygıları dışında kullanmak için herhangi bir sebep var mı?

Elbette, değişmez veri yapılarını kullanmanın birçok nedeni vardır. Ölümsüz veri yapıları Kaya. Değişmez veri yapılarını kullanmanın bazı iyi nedenleri:

  • Değişmez veri yapıları, değişebilir veri yapılarından daha mantıklıdır. "Bu liste boş mu?" Diye sorduğun zaman. Ve bir cevap alırsınız, o zaman cevabın sadece şimdi değil, sonsuza kadar doğru olduğunu bilirsiniz. Değişken veri yapılarıyla gerçekten "bu liste boş mu?" Diye sormazsınız. Tek istediğin "bu liste şu anda boş mu?" ve sonra cevabı mantıksal olarak "geçmişte bir noktada bu liste boş muydu?"

Değişmez bir türle ilgili bir soruya verilen yanıtın sonsuza dek gerçekte kalması güvenlikle ilgili anlamlara sahiptir. Bunun gibi bir kodunuz olduğunu varsayalım:

void Frob(Bar bar)
{
    if (!IsSafe(bar)) throw something;
    DoSomethingDangerous(bar);
}

Eğer Bar değişken bir tipse, burada bir yarış durumu vardır; çubuğu başka bir iş parçacığı üzerinde güvensiz yapılabilir sonra çek ama önce tehlikeli bir şey olur. Eğer Bar değişmez bir tipse, sorunun cevabı hep aynı kalır, bu da daha güvenlidir. (Yol içeren bir dizeyi değiştirip değiştiremeyeceğinizi düşünün. sonra güvenlik kontrolü ama öncedosya açıldı, örneğin.)

  • Değişmez veri yapılarını argüman olarak alan ve bunları sonuç olarak döndüren ve hiçbir yan etkisi olmayan yöntemlere “saf yöntemler” denir. Saf yöntemler, daha yüksek hız için artan bellek kullanımı olan ve genellikle muazzam bir şekilde artan hıza sahip olan, hafızaya alınabilir.

  • Değişmez veri yapıları, kilitlenmeden eşzamanlı olarak çoklu dişlerde kullanılabilir. Bir mutasyon karşısında bir nesnenin tutarsız halinin yaratılmasını önlemek için kilitleme vardır, ancak değişmez nesneler mutasyona sahip değildir. (Bazı sözde değişmez veri yapıları mantıksal olarak değişmezdir, fakat aslında kendi içlerinde mutasyonlar yaparlar, örneğin içeriğini değiştirmeyen bir arama tablosu düşünün, ancak bir sonraki sorgunun ne olabileceğini anlayabiliyorsa, iç yapısını yeniden düzenler. Böyle bir veri yapısı otomatik olarak threadafe olmayacaktır.)

  • Yeni bir yapı eskiden inşa edildiğinde iç kısımlarını verimli bir şekilde yeniden kullanabilen değişmez veri yapıları, bir programın halini çok fazla bellek harcamadan "bir anlık görüntü" almayı kolaylaştırır. Bu, geri alma işlemlerini gerçekleştirmeyi önemsiz kılar. Belirli bir program durumuna nasıl ulaştığınızı gösteren hata ayıklama araçlarını yazmayı kolaylaştırır.

  • ve bunun gibi.

Dizeler daha sonra içten diş güvenli midir veya değil mi?

Herkes kurallara göre oynuyorsa, öyle. Birisi güvenli olmayan kod veya özel yansıma kullanırsa artık kural uygulaması yok. Birisi yüksek ayrıcalık kodu kullanıyorsa, o zaman doğru bir şekilde yapıyorlar ve bir dizeyi mutasyona uğratmıyorlar. Güvenli olmayan kodu sadece iyi için çalıştırmak için gücünüzü kullanın; Büyük güç büyük sorumluluk getirir.

Bu yüzden kilitleri kullanmam mı gerekiyor?

Bu garip bir soru. Unutma, kilitler kooperatif. Kilitler sadece çalışırsa herkes Belirli bir nesneye erişim, kullanılması gereken kilitleme stratejisine katılır.

Eğer kilitleri kullanmalısın üzerinde anlaşılan Belirli bir depolama yerindeki belirli bir nesneye erişim için kilitleme stratejisi, kilitleri kullanmaktır. Bu üzerinde anlaşılan kilitleme stratejisi değilse, kilitleri kullanmak anlamsızdır; Birisi arka kapıya doğru yürürken dikkatli bir şekilde ön kapıyı kilitliyor ve kilidini açıyorsunuz.

Güvenli olmayan kod tarafından mutasyona uğradığını bildiğiniz bir diziniz varsa ve tutarsız kısmi mutasyonlar görmek istemiyorsanız ve bu mutasyon sırasında belirli bir kilidi aldığından emin olmayan mutasyon belgelerini yapan kod varsa, evet Bu dizeye erişirken kilitleri kullanmanız gerekir. Ancak bu durum çok nadirdir; İdeal olarak hiç kimse, başka bir iş parçacığı tarafından diğer kod tarafından erişilebilen bir dizeyi işlemek için güvenli olmayan kod kullanmazdı, çünkü bunu yapmak inanılmaz derecede kötü bir fikirdir. Bu yüzden bu kodun tamamen güvenilir olmasını istiyoruz. Bu yüzden böyle bir işlev için C # kaynak kodunun "bu kodun güvensiz olduğunu, dikkatlice gözden geçirdiğini" belirten büyük bir kırmızı bayrak açmasını zorunlu kılıyoruz.


43
2017-08-28 14:53



Az çaba ile kötü olun: Birkaç rastgele TerminateThread aramaları :-) - Richard


Güvensiz kalırsanız, dizeleri C # 'de de (IIRC) değiştirmek mümkündür.


3
2017-08-28 14:33



Evet öyle. Ancak, dizeler iç içe geçtiğinde, dizeleri yapmaya çalışmadan önce nasıl çalıştığı hakkında gerçekten çok şey bilmelisiniz. - Guffa
güvensiz gidiyor bile 640kb daha fazla bellek kullanımına izin veriyor! - jeremy-george


Tamamen değişmez bir tür yoktur, değişmez bir sınıf, dışarıdan herhangi bir kodun değiştirmesine izin vermemesidir. Yansıma veya güvensiz kod kullanarak, yine de değerlerini değiştirebilirsiniz.

Kullanabilirsiniz readonly değişmez bir değişken oluşturmak için anahtar kelime, ancak bu yalnızca değer türleri için çalışır. Bir referans türünde kullanırsanız, yalnızca işaret ettiği nesne değil, korunan referanstır.

Performans ve sağlamlık gibi değişmez tiplerin birkaç nedeni vardır.

Dizelerin değişmez olduğu biliniyor (dış StringBuilder) derleyicinin buna göre optimizasyon yapabileceği anlamına gelir. Derleyici, bir parametre olarak geçirildiğinde değiştirilmesini önlemek için bir dizeyi kopyalamak için kod üretmez.

Değişmez tiplerden oluşturulan nesneler, ayrıca dişler arasında güvenli bir şekilde geçirilebilir. Değiştirilemedikleri için, farklı iş parçacıklarının bunları aynı zamanda değiştirmesi riski yoktur, bu nedenle onlara erişimi eşzamanlamaya gerek yoktur.

Kodlama hatalarını önlemek için ölçülebilir tipler kullanılabilir. Bir değerin değiştirilmemesi gerektiğini biliyorsanız, yanlışlıkla değiştirilemediğinden emin olmak genellikle iyi bir fikirdir.


3
2017-08-28 14:48





Burada işte kara büyü yok. String sınıfı, iç dizeyi değiştirmenize izin veren herhangi bir genel alan, özellik veya yönteme sahip olmadığı için değişmezdir. Dizeyi değiştiren herhangi bir yöntem, yeni bir dize örneğini döndürür. Elbette bunu kendi sınıflarınızla da yapabilirsiniz.


2
2017-08-28 14:46



Evet, bunun olağanüstü bir şey olmadığını anlıyorum ama iplik güvenliğini tamamen unuttum. Ben her zaman kilitlenmeden değişmez tipler kullanabileceğimi düşündüm, şimdi öyle düşünmüyorum. Yani şimdi kafam karıştı: ya başlangıçtan hatalıydım ya da şimdi yanlıyorum (hatta ikisini de). - n535
Elbette, değişmez bir sınıfın nesnesini korumak zorunda değilsiniz. Kimse değiştiremez. Çok pratik olmadığı için, aslında her zaman eski verilerle çalışırsınız. - Hans Passant


Bu mesajları okuyabilirsiniz Kaçırılmamış tipler: faydalarını anlayın ve bunları kullanın

ve Senkronizasyon ağrıları olmadan çok parçalı bir ortamda durumları yönetin

Ayrıca araç NDepend biraz ile birlikte gelir immutable tipleri ve saf yöntemleri ile cop.


1
2017-08-28 14:33





Tamamen değişmez bir tip oluşturabilir miyim?

Evet. Özel alanları ayarlamak, yalnızca özellikleri ve yöntemleri almak için bir kurucu var.

Bu tür kodları performans kaygıları dışında kullanmak için herhangi bir sebep var mı?

Bir örnek: bu tür tiplerde kilitler birden fazla eşzamanlı iş parçacığından güvenle kullanılmasını gerektirmez, bu da doğru kodu yazmayı kolaylaştırır (yanlış yapılması için kilitler yoktur).

Ek: yetkisiz ayrıcalıklı kodun her zaman .NET korumalarını atlatması mümkündür: ya okumaya ve özel alanlara yazma yansıması veya bir nesnenin belleğini doğrudan manipüle etmek için güvenli olmayan kod.

Bu, .NET'in dışında doğrudur, ayrıcalıklı bir süreç (örneğin "Tanrı" ayrıcalıklarından birine sahip bir işlem veya iplik belirteciyle, örneğin Sahip Olma özelliği etkin) herhangi bir başka işlem yükü bölmesine girebilir, rasgele kod çalıştıran konuları enjekte edebilir, okuyabilir veya hafızayı yazma (yürütme engelleme vb. geçersiz kılma dahil). Sistemin bütünlüğü, sistemin sahibinin işbirliği kadar güçlüdür.


1
2017-08-28 14:30



Teşekkürler, kilitler hakkında düşünmedim) - n535
Böyle bir tip, güvenli olmayan kodda değişmez. Hiçbir şey değil güvenli olmayan kodda değişmez; Güvenli olmayan kodda her bir bayt belleğe yazabilirsiniz. - Eric Lippert
Özel alanlara sahip olmanız, güvensiz kodlarda bile değişmez bir şey yapmaz: Yansıma'yı hala kullanabilirsiniz ... - Timwi
@Bryce Wagner: Eğer immutable tipiniz değişebilir bir probleme sahipse, bu değişmez bir tip değildir. - Guffa
@Timwi: Her zaman yansıma veya güvenli olmayan kod kullanarak işlerinizi bozabilirsiniz. Değişmez tiplerin amacı, normal koddaki hatalarla değiştirilememeleridir. - Guffa