C # string başvuru türü?


164

C # "dize" bir başvuru türü olduğunu biliyorum. Bu MSDN'de. Ancak, bu kod olması gerektiği gibi çalışmaz:

class Test
{
    public static void Main()
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(test);
        Console.WriteLine(test);
    }

    public static void TestI(string test)
    {
        test = "after passing";
    }
}

Dizeyi parametre olarak geçirdiğimden ve bir referans türü olduğumdan, çıktı "geçmeden önce" "geçmeli" olmalıdır, ikinci çıktı ifadesi metnin TestI yönteminde değiştiğini tanımalıdır. Ancak, "geçmeden önce" olsun "ref önce değil değer tarafından geçti gibi görünmesini" geçmeden önce "olsun. Dizelerin değişmez olduğunu anlıyorum, ama burada neler olduğunu nasıl açıklayacağını göremiyorum. Neyi kaçırıyorum? Teşekkürler.


Aşağıdaki Jon tarafından atıfta bulunulan makaleye bakın. Bahsettiğiniz davranış C ++ işaretçileri tarafından da çoğaltılabilir.
Sesh

MSDN'de de çok güzel bir açıklama .
Dimi_Pel

Yanıtlar:


211

Dizeye başvuru değere göre iletilir. Bir referansın değere göre geçirilmesi ile bir nesnenin referans olarak geçirilmesi arasında büyük bir fark vardır. "Referans" kelimesinin her iki durumda da kullanılması talihsiz bir durumdur.

Eğer varsa yapmak dize referansı geçmesi tarafından referans beklediğiniz gibi, bu iş olacak:

using System;

class Test
{
    public static void Main()
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(ref test);
        Console.WriteLine(test);
    }

    public static void TestI(ref string test)
    {
        test = "after passing";
    }
}

Şimdi, bir referansın başvurduğu nesnede değişiklik yapmak ve farklı bir nesneye başvurmasına izin vermek için bir değişkende (parametre gibi) bir değişiklik yapmak arasında ayrım yapmanız gerekir. Bir dizede değişiklik yapamayız çünkü dizeler değişmezdir, ancak StringBuilderbunun yerine bir diziyle gösterebiliriz :

using System;
using System.Text;

class Test
{
    public static void Main()
    {
        StringBuilder test = new StringBuilder();
        Console.WriteLine(test);
        TestI(test);
        Console.WriteLine(test);
    }

    public static void TestI(StringBuilder test)
    {
        // Note that we're not changing the value
        // of the "test" parameter - we're changing
        // the data in the object it's referring to
        test.Append("changing");
    }
}

Daha fazla ayrıntı için parametre aktarma hakkındaki makaleme bakın .


2
katılıyorum, sadece ref değiştiriciyi kullanmanın referans olmayan türler için de işe yaradığını, yani her ikisinin de oldukça ayrı kavramlar olduğunu açıkça belirtmek isteriz.
eglasius

2
@ Jon Skeet makalenizdeki sidenote'u çok sevdi. Sen vermeliydim referencedcevabınız olarak o
Nithish Inpursuit Ofhappiness

36

Soruyu cevaplamamız gerekirse: String bir referans türüdür ve referans olarak davranır. Gerçek dizeye değil referansa sahip bir parametre iletiyoruz. Sorun fonksiyonda:

public static void TestI(string test)
{
    test = "after passing";
}

Parametre testdizeye bir referans tutar, ancak bir kopyadır. Dizeye işaret eden iki değişkenimiz var. Ve dizelerle yapılan herhangi bir işlem aslında yeni bir nesne oluşturduğundan, yerel kopyanızı yeni dizeye işaret edecek şekilde yaparız. Ancak orijinal testdeğişken değiştirilmez.

refİşlev bildirimine ve çağırma işine konulması için önerilen çözümler, çünkü testdeğişkenin değerini geçmeyeceğiz, ancak ona sadece bir referans geçeceğiz. Böylece fonksiyon içindeki herhangi bir değişiklik orijinal değişkeni yansıtacaktır.

Sonunda tekrarlamak istiyorum: Dize bir başvuru türüdür, ancak onun değişmez beri çizgi test = "after passing";aslında yeni bir nesne oluşturur ve değişken bizim kopya testyeni dize işaret edecek şekilde değiştirildi.


25

Diğerlerinin belirttiği gibi, String.NET'teki tür değiştirilemez ve başvurusu değere göre iletilir.

Orijinal kodda, bu satır yürütülür yürütülmez:

test = "after passing";

Daha sonra testartık edilir atıfta orijinal nesneye. Yeni bir String nesne oluşturduk testve yönetilen öbek üzerinde bu nesneye başvurmak üzere atandık.

Birçok insanın buraya takıldığını hissediyorum çünkü onlara hatırlatacak görünür bir resmi kurucu yok. Bu durumda, Stringtürün nasıl oluşturulduğu konusunda dil desteğine sahip olduğu için perde arkasında gerçekleşiyor .

Bu nedenle, yöntemin testkapsamının dışında görünmesi bu yüzden görünmüyor TestI(string)- referansı değere göre geçtik ve şimdi bu değer değişti! Ancak Stringreferans referansla geçildiyse, referans değiştiğinde referansı TestI(string)yöntemin kapsamı dışında göreceğiz .

Ya ref veya out anahtar kelime bu durumda ihtiyaç vardır. Hissettiğim outanahtar kelime biraz daha iyi bu özel durum için uygun olabilir.

class Program
{
    static void Main(string[] args)
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(out test);
        Console.WriteLine(test);
        Console.ReadLine();
    }

    public static void TestI(out string test)
    {
        test = "after passing";
    }
}

ref = dış işlev başlatılmış, dış = işlevin içinden başlatılmış veya başka bir deyişle; ref iki yönlüdür, sadece dışarıdadır. Bu yüzden kesinlikle ref kullanılmalıdır.
Paul Zahra

@PaulZahra: outkodun derlenmesi için yöntem içinde atanmalıdır. refböyle bir gereksinimi yoktur. Ayrıca outparametreler yöntemin dışında başlatılır - bu yanıttaki kod bir karşı örnektir.
Derek W

- netleştirmek Should outparametreler edilebilir yöntemin dışında başlatıldı, fakat gerekmez. Bu durumda, .NET'te türün outdoğası hakkında bir nokta göstermek için parametreyi başlatmak istiyoruz string.
Derek W

9

Aslında bu konu için herhangi bir nesne için aynı olurdu, yani bir referans türü olmak ve referans ile geçmek c # 2 farklı şeylerdir.

Bu işe yarar, ancak bu türden bağımsız olarak geçerlidir:

public static void TestI(ref string test)

Ayrıca dize bir başvuru türü olmak, onun da özel bir tür. Değişmez olarak tasarlandığından, tüm yöntemleri örneği değiştirmez (yeni bir tane döndürür). Ayrıca performans için bazı ekstra şeyler var.


7

Değer türleri, değere göre geçiş, referans türleri ve referansla geçiş arasındaki farkı düşünmenin iyi bir yolu:

Değişken bir kaptır.

Bir değer türü değişkeni bir örnek içerir. Bir başvuru türü değişkeni, başka bir yerde saklanan bir örneğin işaretçisi içerir.

Bir değer türü değişkenini değiştirmek içerdiği örneği değiştirir. Bir başvuru türü değişkeninin değiştirilmesi, işaret ettiği örneği değiştirir.

Ayrı başvuru türü değişkenleri aynı örneği gösterebilir. Bu nedenle, aynı örnek, ona işaret eden herhangi bir değişken aracılığıyla mutasyona uğratılabilir.

Bir by-pass değeri bağımsız değişkeni, içeriğin yeni bir kopyasına sahip yeni bir kapsayıcıdır. Başarılı bir bağımsız değişken, orijinal içeriğine sahip orijinal kapsayıcıdır.

Bir değer türü bağımsız değişkeni değere göre geçtiğinde: Kapsayıcı benzersiz olduğu için bağımsız değişkenin içeriğini yeniden atamanın kapsam dışında bir etkisi olmaz. Örnek bağımsız bir kopya olduğu için, bağımsız değişkeni değiştirmenin kapsam dışında bir etkisi yoktur.

Bir başvuru türü bağımsız değişkeni değere göre iletildiğinde: Bağımsız değişkenin içeriğini yeniden atamanın, kapsayıcı benzersiz olduğu için kapsam dışında bir etkisi olmaz. Kopyalanan işaretçi paylaşılan bir örneği işaret ettiğinden, bağımsız değişkenin içeriğini değiştirmek harici kapsamı etkiler.

Herhangi bir bağımsız değişken başvuru yoluyla iletildiğinde: Bağımsız değişkenin içeriğini yeniden atamak, kapsayıcı paylaşıldığı için dış kapsamı etkiler. Bağımsız değişkenin içeriğinin değiştirilmesi, içerik paylaşıldığı için dış kapsamı etkiler.

Sonuç olarak:

Dize değişkeni, başvuru türü bir değişkendir. Bu nedenle, başka bir yerde saklanan bir örneğe bir işaretçi içerir. Değere göre iletildiğinde, işaretçisi kopyalanır, bu nedenle bir dize bağımsız değişkenini değiştirmek paylaşılan örneği etkiler. Ancak, bir dize örneğinin değiştirilebilir özellikleri olmadığından, bir dize bağımsız değişkeni yine de değiştirilemez. Referansla iletildiğinde, işaretçinin kapsayıcısı paylaşılır, bu nedenle yeniden atama yine de dış kapsamı etkiler.


6

" Bir resim bin kelimeye bedeldir ".

Burada basit bir örneğim var, bu sizin durumunuza benzer.

string s1 = "abc";
string s2 = s1;
s1 = "def";
Console.WriteLine(s2);
// Output: abc

Olan şey bu:

resim açıklamasını buraya girin

  • Satır 1 ve 2: s1ve s2değişkenler aynı referans"abc" dize nesnesine .
  • Satır 3: Dizeler değiştirilemediğinden , "abc"dize nesnesi kendisini (to "def") değiştirmez , "def"bunun yerine yeni bir dize nesnesi oluşturulur ve sonras1 ona başvurur.
  • Satır 4: s2Hala "abc"dize nesnesine başvuruyor , bu yüzden çıktı.

5

Yukarıdaki cevaplar yararlıdır, ben sadece ref anahtar sözcüğü olmadan parametre geçtiğimizde, bu parametre bir referans türü olsa bile ne olacağını açıkça gösteren bir örnek eklemek istiyorum:

MyClass c = new MyClass(); c.MyProperty = "foo";

CNull(c); // only a copy of the reference is sent 
Console.WriteLine(c.MyProperty); // still foo, we only made the copy null
CPropertyChange(c); 
Console.WriteLine(c.MyProperty); // bar


private void CNull(MyClass c2)
        {          
            c2 = null;
        }
private void CPropertyChange(MyClass c2) 
        {
            c2.MyProperty = "bar"; // c2 is a copy, but it refers to the same object that c does (on heap) and modified property would appear on c.MyProperty as well.
        }

1
Bu açıklama benim için en iyisi oldu. Bu nedenle, ref anahtar sözcüğünü (veya dışını) kullanmadığımız sürece, değişkenin kendisinin değer veya referans türü olmasına rağmen her şeyi değere göre geçiririz. Günlük kodlamamızda bu önemli değildir, çünkü nesnelerimizi genellikle null olarak veya geçildikleri bir yöntem içinde farklı bir örneğe ayarlamıyoruz, bunun yerine özelliklerini ayarlıyoruz veya yöntemlerini çağırıyoruz. "String" in durumunda, bunu yeni bir örneğe ayarlamak her zaman gerçekleşir, ancak yeni güncelleme görünmez ve bu da eğitimsiz göz için yanlış bir yorum verir. Yanlışsa beni düzeltin.
И Г И І И О

3

Meraklı zihinler ve konuşmayı tamamlamak için: Evet, String bir referans türüdür :

unsafe
{
     string a = "Test";
     string b = a;
     fixed (char* p = a)
     {
          p[0] = 'B';
     }
     Console.WriteLine(a); // output: "Best"
     Console.WriteLine(b); // output: "Best"
}

Ancak bu değişikliğin yalnızca güvensiz bir blokta işe yaradığını unutmayın ! çünkü Dizeler değiştirilemez (MSDN'den):

Bir dizgi nesnesinin içeriği, nesne oluşturulduktan sonra değiştirilemez, ancak sözdizimi bunu sanki bunu yapabiliyormuş gibi görünmesini sağlar. Örneğin, bu kodu yazdığınızda, derleyici aslında yeni karakter dizisini tutmak için yeni bir dize nesnesi oluşturur ve bu yeni nesne b'ye atanır. Daha sonra "h" dizesi çöp toplama için uygundur.

string b = "h";  
b += "ello";  

Ve şunu unutmayın:

Dize bir başvuru türü olmasına rağmen, eşitlik işleçleri ( ==ve !=) başvuruları değil, dize nesnelerinin değerlerini karşılaştırmak için tanımlanır.


0

Kodunuzun aşağıdakine benzer olduğuna inanıyorum ve değerin burada olmayacağı için aynı nedenle değişmesini beklememeliydiniz:

 public static void Main()
 {
     StringWrapper testVariable = new StringWrapper("before passing");
     Console.WriteLine(testVariable);
     TestI(testVariable);
     Console.WriteLine(testVariable);
 }

 public static void TestI(StringWrapper testParameter)
 {
     testParameter = new StringWrapper("after passing");

     // this will change the object that testParameter is pointing/referring
     // to but it doesn't change testVariable unless you use a reference
     // parameter as indicated in other answers
 }

-1

Deneyin:


public static void TestI(ref string test)
    {
        test = "after passing";
    }

3
Cevabınız sadece koddan fazlasını içermelidir. Ayrıca neden işe yaradığına dair bir açıklama da içermelidir.
Charles Caldwell
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.