.NET'te dizeler nasıl geçirilir?


121

Bir stringişleve a ilettiğimde, dizenin içeriğine bir işaretçi iletilir mi, yoksa dizenin tamamı işleve bir yığın üzerinde işleve iletilir structmi?

Yanıtlar:


278

Bir referans aktarılır; ancak teknik olarak referans yoluyla geçilmez. Bu ince ama çok önemli bir ayrımdır. Aşağıdaki kodu göz önünde bulundurun:

void DoSomething(string strLocal)
{
    strLocal = "local";
}
void Main()
{
    string strMain = "main";
    DoSomething(strMain);
    Console.WriteLine(strMain); // What gets printed?
}

Burada ne olduğunu anlamak için bilmeniz gereken üç şey var:

  1. Dizeler C # 'da başvuru türleridir.
  2. Onlar da değişmezdir, bu yüzden ne zaman ipi değiştiriyormuşsun gibi görünen bir şey yapsan, değişmiyorsun. Tamamen yeni bir dizge oluşturulur, referans ona işaret edilir ve eskisi atılır.
  3. Dizeler başvuru türü olsa da başvuru ile strMainaktarılmaz. Bu bir başvuru türüdür, ancak başvurunun kendisi değere göre aktarılır . refAnahtar kelime olmadan bir parametreyi outher ilettiğinizde ( parametreleri saymadan ), bir şeyi değere göre geçirmiş olursunuz.

Bu demek oluyor ki ... değere göre bir referans geçiyorsun. Bir referans türü olduğundan, yalnızca referans yığına kopyalandı. Ama bu ne anlama geliyor?

Referans türlerini değere göre geçirme: Bunu zaten yapıyorsunuz

C # değişkenleri başvuru türleri veya değer türleridir . C # parametreleri ya başvuruya göre ya da değere göre geçirilir . Terminoloji burada bir sorundur; bunlar aynı şey gibi geliyor ama değiller.

HERHANGİ türde bir parametre geçirirseniz ve refanahtar kelimeyi kullanmazsanız , o zaman onu değere göre geçirmişsinizdir. Değerine göre geçtiyseniz, gerçekten geçtiğiniz şey bir kopyadır. Ancak parametre bir referans tipiyse, kopyaladığınız şey referanstır, işaret ettiği şey değil.

İşte Mainyöntemin ilk satırı :

string strMain = "main";

Bu satırda iki şey yarattık: bir mainyerde hafızada saklanan değere sahip bir dize ve ona strMainişaret eden bir referans değişkeni .

DoSomething(strMain);

Şimdi bu referansı aktarıyoruz DoSomething. Değerine göre geçtik, yani bir kopya çıkardık. Bu bir referans türüdür, yani dizeyi değil referansı kopyaladık. Şimdi bellekte her biri aynı değeri gösteren iki referansımız var.

Aranan ucun içinde

İşte DoSomethingyöntemin en tepesi :

void DoSomething(string strLocal)

Hiçbir refanahtar kelime, bu yüzden strLocalve strMainaynı değerde işaret iki farklı referanslar vardır. Yeniden atarsak strLocal...

strLocal = "local";   

... saklanan değeri değiştirmedik; denilen referansı aldık strLocalve yepyeni bir dizgiye hedefledik. Bunu strMainyaptığımızda ne olur ? Hiçbir şey değil. Hala eski dizgiye işaret ediyor.

string strMain = "main";    // Store a string, create a reference to it
DoSomething(strMain);       // Reference gets copied, copy gets re-pointed
Console.WriteLine(strMain); // The original string is still "main" 

Değişmezlik

Senaryoyu bir saniyeliğine değiştirelim. Dizelerle çalışmadığımızı, ancak oluşturduğunuz sınıf gibi değiştirilebilir bir referans türüyle çalıştığımızı hayal edin.

class MutableThing
{
    public int ChangeMe { get; set; }
}

Gösterdiği nesnenin referansını takip ederseniz objLocal, özelliklerini değiştirebilirsiniz:

void DoSomething(MutableThing objLocal)
{
     objLocal.ChangeMe = 0;
} 

MutableThingHafızada hala sadece bir tane var ve hem kopyalanan referans hem de orijinal referans hala onu gösteriyor. Kendisinin özellikleri MutableThingdeğişti :

void Main()
{
    var objMain = new MutableThing();
    objMain.ChangeMe = 5; 
    Console.WriteLine(objMain.ChangeMe); // it's 5 on objMain

    DoSomething(objMain);                // now it's 0 on objLocal
    Console.WriteLine(objMain.ChangeMe); // it's also 0 on objMain   
}

Ah, ama dizeler değişmez! ChangeMeAyarlanacak bir özellik yok . Sen yapamazsın strLocal[3] = 'H'sen C tarzı ile olabilir gibi C # chardizide; bunun yerine tamamen yeni bir dizi oluşturmanız gerekir. Değiştirmenin tek yolu strLocalreferansı başka bir dizeye yönlendirmektir ve bu, yapacağınız hiçbir şeyin strLocaletkileyemeyeceği anlamına gelir strMain. Değer sabittir ve referans bir kopyadır.

Referans ile bir referansın aktarılması

Bir fark olduğunu kanıtlamak için, referansla bir referansı ilettiğinizde şunlar olur :

void DoSomethingByReference(ref string strLocal)
{
    strLocal = "local";
}
void Main()
{
    string strMain = "main";
    DoSomethingByReference(ref strMain);
    Console.WriteLine(strMain);          // Prints "local"
}

Bu sefer, dizge Maingerçekten değişiyor çünkü referansı yığına kopyalamadan geçtiniz.

Dolayısıyla dizeler referans türler olsa bile, onları değere göre geçirmek, aranan uçta olup bitenlerin çağıran dizgeyi etkilemeyeceği anlamına gelir. Ancak referans türler oldukları için , dolaştırmak istediğinizde tüm dizeyi bellekte kopyalamanıza gerek yoktur.

Diğer kaynaklar:


3
@TheLight - Maalesef burada "Bir referans türü varsayılan olarak referans olarak aktarılır" dediğinizde yanılıyorsunuz. Varsayılan olarak, tüm parametreler değere göre aktarılır, ancak başvuru türlerinde bu , başvurunun değere göre iletildiği anlamına gelir . Referans türlerini referans parametreleriyle karıştırıyorsunuz ki bu anlaşılabilir bir durum çünkü bu çok kafa karıştırıcı bir ayrım. Bkz burada Değer bölümünde önünden geçerken Başvuru türleri. Bağlantılı makaleniz oldukça doğru, ama aslında benim görüşümü destekliyor.
Justin Morgan

1
@JustinMorgan Ölü bir yorum dizisini gündeme getirmek için değil, ama TheLight'ın yorumunun C'de düşündüğünüzde mantıklı olduğunu düşünüyorum. C'de, veriler sadece bir bellek bloğu. Bir referans, bu bellek bloğuna bir göstericidir. Tüm bellek bloğunu bir işleve aktarırsanız, buna "değere göre geçiş" denir. İşaretçiyi geçerseniz buna "referansla geçiş" denir. C # 'da, bellek bloğunun tamamında geçiş kavramı yoktur, bu nedenle "değerle geçiş" i işaretçiyi içeri geçirmek anlamına gelecek şekilde yeniden tanımladılar. Bu yanlış görünüyor, ancak bir işaretçi de sadece bir bellek bloğu! Bana göre terminoloji oldukça keyfi
rliu

@roliu - Sorun şu ki, C'de çalışmıyoruz ve C #, benzer adı ve sözdizimine rağmen son derece farklı. Birincisi, referanslar işaretçilerle aynı şey değildir ve onları bu şekilde düşünmek tuzaklara yol açabilir. Yine de en büyük sorun, "referansla geçirme" nin C # 'da anahtar kelimeyi gerektiren çok özel bir anlamı olmasıdır ref. Referansla geçişin
Justin Morgan

1
@JustinMorgan C ve C # terminolojisinin karıştırılmasının kötü olduğuna katılıyorum, ancak lippert'in gönderisinden keyif alsam da, referansları özellikle işaretçi olarak düşünmenin burada herhangi bir şeyi bulanıklaştırdığını kabul etmiyorum. Blog yazısı, bir referansı bir işaretçi olarak düşünmenin ona nasıl çok fazla güç verdiğini açıklıyor. Ben farkındayım refanahtar kelime yarar vardır, ben sadece bir nedenini anlatmaya çalışıyordu olabilir C # değeri tarafından bir referans türü geçen düşünmek referans olarak geçen (ve bir referans türü geçme "geleneksel" (yani C) kavramı gibi görünüyor C # 'da referans olarak, değere göre bir referansa bir referans iletmek gibi görünür).
rliu

2
Doğru, ama ben @roliu bir işlev gibi nasıl başvuran düşünüyorum Foo(string bar)olarak düşünülebilir olabilir Foo(char* bar)oysa Foo(ref string bar)olacağını Foo(char** bar)(ya da Foo(char*& bar)ya da Foo(string& bar)C ++). Elbette, bunu her gün nasıl düşünmeniz gerektiği değil, ama nihayet kaputun altında neler olduğunu anlamama yardımcı oldu.
Cole Johnson

23

C # 'teki dizeler değişmez referans nesneleridir. Bu, onlara yapılan referansların (değere göre) aktarıldığı ve bir dize oluşturulduktan sonra onu değiştiremeyeceğiniz anlamına gelir. Dizenin değiştirilmiş sürümlerini (alt dizeler, kırpılmış sürümler, vb.) Üreten yöntemler, orijinal dizenin değiştirilmiş kopyalarını oluşturur .


10

Dizeler özel durumlardır. Her örnek değişmezdir. Bir dizenin değerini değiştirdiğinizde, belleğe yeni bir dizge ayırmış olursunuz.

Dolayısıyla, işlevinize yalnızca referans iletilir, ancak dize düzenlendiğinde yeni bir örnek olur ve eski örneği değiştirmez.


4
Dizeler bu açıdan özel bir durum değildir . Aynı semantiğe sahip olabilen değişmez nesneler yaratmak çok kolaydır. (Yani, onu değiştirecek bir yöntemi ortaya çıkarmayan bir türün örneği ...)

Dizeler özel durumlardır - değer türleri gibi davrandıkları için değiştirilebilir gibi görünen, etkili bir şekilde değişmez referans türleridir.
Enigmativity

1
@Enigmativity Bu mantıkla Uri(sınıf) ve Guid(yapı) da özel durumlardır. System.StringBir "değer türü" gibi davrandığını, sınıf veya yapı kökenli diğer değişmez türlerden daha fazla görmüyorum .

3
@pst - Dizelerin özel oluşturma semantiği vardır - Uri&'nin aksine Guid- bir dize değişkenine bir dize-değişmez değer atayabilirsiniz. Dize, intyeniden atanmış gibi değiştirilebilir gibi görünür , ancak örtük olarak bir nesne yaratır - newanahtar kelime olmadan .
Enigmativity

3
String özel bir durumdur, ancak bunun bu soruyla hiçbir ilgisi yoktur. Değer türü, başvuru türü, hangi tür olursa olsun, bu soruda hepsi aynı şekilde davranacaktır.
Kirk Broadhurst
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.