Bir nesneyi iletirken neden 'ref' anahtar kelimesini kullanalım?


290

Bir nesneyi bir yönteme geçiriyorum, neden ref anahtar sözcüğünü kullanmalıyım? Bu zaten varsayılan davranış değil mi?

Örneğin:

class Program
{
    static void Main(string[] args)
    {
        TestRef t = new TestRef();
        t.Something = "Foo";

        DoSomething(t);
        Console.WriteLine(t.Something);
    }

    static public void DoSomething(TestRef t)
    {
        t.Something = "Bar";
    }
}


public class TestRef
{
    public string Something { get; set; }
}

Çıktı "Bar" dır, bu da nesnenin referans olarak geçtiği anlamına gelir.

Yanıtlar:


299

refNesnenin ne olduğunu değiştirmek istiyorsanız a değerini iletin :

TestRef t = new TestRef();
t.Something = "Foo";
DoSomething(ref t);

void DoSomething(ref TestRef t)
{
  t = new TestRef();
  t.Something = "Not just a changed t, but a completely different TestRef object";
}

DoSomething çağırdıktan sonra t, orijinali ifade etmez new TestRef, ancak tamamen farklı bir nesneyi ifade eder.

Bu, değişmez bir nesnenin değerini değiştirmek istiyorsanız, örneğin a string. Bir stringkez oluşturulduktan sonra değerini değiştiremezsiniz . Ancak a kullanarak ref, farklı bir değere sahip başka bir işlev için dizeyi değiştiren bir işlev oluşturabilirsiniz.

Düzenleme: Diğer insanların belirttiği gibi. refGerekmedikçe kullanmak iyi bir fikir değildir . Kullanarak refyönteme başka bir şey için argümanı değiştirme özgürlüğü verir, yöntemin arayanların bu olasılığı ele almaları için kodlanması gerekir.

Ayrıca, parametre türü bir nesne olduğunda, nesne değişkenleri her zaman nesneye referans olarak işlev görür. Bu, refanahtar kelime kullanıldığında bir referansa referansınız olduğu anlamına gelir . Bu, yukarıda verilen örnekte açıklandığı gibi işleri yapmanızı sağlar. Ancak, parametre türü ilkel bir değer olduğunda (örn. int), Bu parametre yöntem içinde atanırsa, yöntem döndürüldükten sonra iletilen bağımsız değişkenin değeri değişir:

int x = 1;
Change(ref x);
Debug.Assert(x == 5);
WillNotChange(x);
Debug.Assert(x == 5); // Note: x doesn't become 10

void Change(ref int x)
{
  x = 5;
}

void WillNotChange(int x)
{
  x = 10;
}

88

"Bir referansı değere göre geçirme" ile "bir parametreyi / bağımsız değişkeni referansla geçirme" arasında ayrım yapmanız gerekir.

Bu haber gruplarında her seferinde dikkatlice yazmak zorunda kalmamak için konuyla ilgili oldukça uzun bir makale yazdım :)


1
Peki .Net C # koduna VB6 yükseltme sırasında sorunla karşılaştı. Ref, out ve plain parametrelerini alan işlev / yöntem imzaları vardır. Öyleyse, düz bir param ile ref arasındaki farkı nasıl daha iyi ayırt edebiliriz?
bonCodigo

2
@bonCodigo: "Daha iyi ayırt etmek" ile ne demek istediğinizden emin değilsiniz - imzanın bir parçası refve çağrı sitesinde de belirtmeniz gerekiyor ... başka nerede ayırt edilmesini istersiniz? Anlambilim de oldukça açıktır, ancak dikkatle ifade edilmesi gerekir (ortak aşırı basitleştirme olan "nesneler referansla geçirilir" yerine).
Jon Skeet

neden visual studio hala açıkça ne geçtiğini bilmiyorum
MonsterMMORPG

3
@MonsterMMORPG: Bununla ne demek istediğini bilmiyorum, korkuyorum.
Jon Skeet

57

.NET'te herhangi bir parametreyi bir yönteme geçirdiğinizde, bir kopya oluşturulur. Değer türlerinde, değerde yaptığınız herhangi bir değişikliğin yöntem kapsamında olduğu ve yöntemden çıktığınızda kaybolacağı anlamına gelir.

Bir Referans Türü geçirirken, bir kopya da yapılır, ancak referansın bir kopyasıdır, yani artık aynı nesneye bellekte İKİ referansınız vardır. Bu nedenle, nesneyi değiştirmek için başvuruyu kullanırsanız, değiştirilir. Ancak referansın kendisini değiştirirseniz - bunun bir kopya olduğunu hatırlamamız gerekir - yöntemden çıkıldığında yapılan değişiklikler de kaybolur.

İnsanların daha önce söylediği gibi, bir ödev referansın bir modifikasyonudur, bu nedenle kaybolur:

public void Method1(object obj) {   
 obj = new Object(); 
}

public void Method2(object obj) {  
 obj = _privateObject; 
}

Yukarıdaki yöntemler orijinal nesneyi değiştirmez.

Örneğinizde küçük bir değişiklik

 using System;

    class Program
        {
            static void Main(string[] args)
            {
                TestRef t = new TestRef();
                t.Something = "Foo";

                DoSomething(t);
                Console.WriteLine(t.Something);

            }

            static public void DoSomething(TestRef t)
            {
                t = new TestRef();
                t.Something = "Bar";
            }
        }



    public class TestRef
    {
    private string s;
        public string Something 
        { 
            get {return s;} 
            set { s = value; }
        }
    }

6
Bu cevabı kabul edilen cevaptan daha çok seviyorum. Ref anahtar sözcüğüyle bir referans türü değişkeni iletilirken neler olduğunu daha açık bir şekilde açıklar. Teşekkür ederim!
Stefan

17

TestRef bir sınıf olduğundan (referans nesnelerdir), t içindeki içeriği ref olarak iletmeden değiştirebilirsiniz. Ancak, bir ref olarak t iletirseniz, TestRef orijinal t'in ne anlama geldiğini değiştirebilir. yani farklı bir nesneye işaret etmesini sağlayın.


16

İle refyazabilirsiniz:

static public void DoSomething(ref TestRef t)
{
    t = new TestRef();
}

Ve yöntem tamamlandıktan sonra t değiştirilecektir.


8

fooReferans türlerinin (örn. List<T>) Değişkenlerini (örn. ) "Object # 24601" formundaki nesne tanımlayıcılarını tutma olarak düşünün . İfadenin "Object # 24601" (dört öğeden oluşan bir liste) içermesine foo = new List<int> {1,5,7,9};neden olduğunu varsayalım foo. Daha sonra çağırma foo.LengthNesne # 24601'in uzunluğunu soracaktır ve 4'e yanıt verecektir, bu yüzden foo.Length4'e eşit olacaktır.

Kullanılmadan foobir yönteme geçirilirse ref, bu yöntem Nesne # 24601'de değişiklik yapabilir. Bu tür değişikliklerin bir sonucu olarak, foo.Lengthartık 4 eşit olmayabilir. Ancak, yöntemin kendisi değişemez foove "Object # 24601" tutmaya devam eder.

Geçme foobir şekilde refparametre marka denilen yöntem sadece Nesne # 24601 değil değiştirir, ama aynı zamanda sağlayacak fookendisi. Yöntem yeni bir Nesne # 8675309 oluşturabilir ve buna bir başvuru depolayabilir foo. Eğer öyleyse, fooartık "Object # 24601" değil, "Object # 8675309" tutulur.

Uygulamada, başvuru türü değişkenleri "Object # 8675309" biçiminde dizeleri tutmaz; anlamlı bir şekilde sayıya dönüştürülebilecek hiçbir şey tutmazlar. Her başvuru türü değişkeni bir bit kalıbı tutacak olsa da, bu değişkenlerde depolanan bit kalıpları ve tanımladıkları nesneler arasında sabit bir ilişki yoktur. Kodun bir nesneden veya ona yapılan bir referanstan bilgi almasının ve daha sonra kod orijinal nesneyi tanımlayan bir referansın tutulması ya da bilinmediği sürece başka bir referansın aynı nesneyi tanımlayıp tanımlamadığını belirlemenin hiçbir yolu yoktur.


5

Bu, C'de bir işaretçiye bir işaretçi geçirmeye benzer. .NET'te bu, orijinal T'nin ne ifade ettiğini değiştirmenize izin verir, kişisel olarak düşünüyorum, eğer .NET'te yapıyorsanız muhtemelen bir tasarım sorununuz var demektir!


3

refAnahtar kelime ile referans türlerini kullanarak, referansa etkili bir şekilde bir referans geçiyorsunuz. Birçok açıdan, outanahtar kelimeyi kullanmakla aynıdır, ancak küçük farkla, yöntemin ref'ed parametresine gerçekten bir şey atayacağının garantisi yoktur .


3

ref sadece iki kapsam için küresel bir alan olarak taklit eder (veya davranır):

  • Arayan
  • Callee.

1

Ancak bir değeri geçiyorsanız işler farklıdır. Bir değeri referans olarak iletilmeye zorlayabilirsiniz. Bu, örneğin bir yönteme bir tam sayı iletmenize ve yöntemin sizin adınıza tamsayıyı değiştirmesine olanak tanır.


4
Bir başvuru veya değer türü değeri geçiriyor olsanız da, varsayılan davranış değere göre geçmektir. Sadece değer Sen geçen referans türleri ile anlamak gerekir olduğunu bir başvuru. Bu geçen aynı değildir ile referans.
Jon Skeet

1

Ref, işlevin ellerini nesnenin kendisine mi yoksa yalnızca değerine mi alıp alamayacağını belirtir.

Referans yoluyla geçmek bir dile bağlı değildir; by-pass-value, name by pass, need by pass vb. yanında bir parametre bağlayıcı stratejidir.

Bir sidenote: sınıf adı TestRefbu bağlamda çok kötü bir seçimdir;).

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.