2013-03-10 5 views
10

Как мы все знаем, объект классов C# рассматривается как ссылка, так что происходит, когда вы передаете ссылочный объект в качестве ссылки на метод? Скажем, у нас есть:Ссылка на ссылку в C#?

public class A { ... } 

, а затем:

public void F(ref A a) { ... } 

Находит ли компилятор, что a уже ссылочный тип и сохранить его таким образом, или он создает новую ссылку на этот объект?

А что, если у нас есть что-то вроде этого:

public void F(ref A a) 
{ 
    F(ref a); 
} 

В этом коде, помимо очевидного StackOverflowException, делает компилятор создает ссылку на ссылку ... ссылаться на a, который является ссылкой на объект?

+0

Также см. Например [Почему используется ключевое слово ref при передаче объекта?] (Http://stackoverflow.com/questions/186891/) и потоки в разделе «Связанные» столбца справа, при этом стр. –

ответ

25

Это лучше всего иллюстрируется на примере:

public class C { public int P { get; set; } } 
public class X 
{ 
    static void M(C c1, C c2, ref C c3, ref C c4) 
    { 
     c1.P = 11; 
     c2 = new C() { P = 12 }; 
     c3.P = 13; 
     c4 = new C() { P = 14 }; 
    } 
    static void Main() 
    { 
     C q1 = new C() { P = 1 }; 
     C q2 = new C() { P = 2 }; 
     C q3 = new C() { P = 3 }; 
     C q4 = new C() { P = 4 }; 
     M(q1, q2, ref q3, ref q4); 
     Console.WriteLine(q1.P); 
     Console.WriteLine(q2.P); 
     Console.WriteLine(q3.P); 
     Console.WriteLine(q4.P); 
    } 
} 

Что происходит?

q1 и c1 относятся к одному и тому же объекту, но имеют разные переменные. Мутирование c1.P мутирует q1.P, поскольку обе переменные относятся к одному и тому же объекту, поэтому q1 теперь 11.

q2 и c2 относятся к одному и тому же объекту, но представляют собой разные переменные. Мутирование c2 не мутирует q2, так как c2 и q2 - разные переменные; изменение одного не меняет другого. q2 остается 2, и новый объект потерян.

q3 и c3 - это два имени для одной и той же переменной и, следовательно, относятся к одному и тому же объекту. Когда вы меняете c3.P, который автоматически меняет q3.P, потому что это два имени для одной и той же вещи.

q4 и c4 - это два имени для одной и той же переменной, поэтому мутация q4 также мутирует c4.

Это имеет смысл?

К сожалению, ключевое слово для "make a alias to this variable" is "ref". Было бы более ясно, был ли это «псевдоним».

Чтобы ответить на второй вопрос: нет, это не делает цепочку ссылок. Давайте сделаем более четкий пример:

... 
int c1 = 123; 
M(ref c1); 
... 
void M1(ref int q1) { M2(ref q1); } 
void M2(ref int q2) { M2(ref q2); } 

Это говорит о том, что c1 и q1 разные названия одной и той же переменной, а q1 и q2 разные названия одной и той же переменной, и, следовательно, c1, q1 и q2 все псевдонимы друг для друга. В C# никогда не существует ссылки на ссылку на переменную, как в C++.

+0

Ваш первый метод также можно назвать как «M (q1, q2, ref qSame, ref qSame)», а затем мы видим, что важно, чтобы строка 'c3.P = 13;' находилась перед строкой 'c4 = новый C() {P = 14}; 'внутри этого метода. –

+0

Большое спасибо, отличный ответ !!! Я бы никогда не думал о 'ref', как' alias', это правда, что вы говорите о проблеме именования, концепция может быть неправильно понята. Еще раз спасибо. – ecampver

+2

@ e.campver: Дело в том, что в системе типа CLR существуют ссылочные типы, и существуют типы «ссылки на переменные», и они логически различны. Параметр ref имеет вид системы типа CLR типа «ссылка на переменную», но с точки зрения системы типа C# это больше похоже на псевдоним. –

1

Проще говоря, передача переменной в качестве параметра ref как создание псевдонима для исходной переменной.

Референтные типы и ссылочные параметры представляют собой различные объекты. В C# переменные всегда передаются по значению. Это значение может быть ссылкой на другой объект или сохраненное значение.

Другими словами, ссылочные типы «передаются по ссылке», потому что, когда вы передаете экземпляр объекта методу, метод получает ссылку на экземпляр объекта.
В случае ссылочных параметров ссылка на переменную (следовательно, имеет смысл подумать об этом как псевдоним). Это другая форма «передачи по ссылке».

Идя ваш пример:

public void F(ref A a) 
{ 
    F(ref a); 
} 

Здесь его, как у нас есть один объект (оригинал параметра a), на который ссылается бесконечное число раз. (Обратите внимание, что это не то, что фактически происходит). Эта диаграмма призвана обеспечить идиоматическое представление того, что происходит под обложками при работе с эталонными параметрами.

enter image description here

См раздел 1.6.6.1 на 4,0 C# спецификации для получения дополнительной информации.

+0

Нет, 'a' не является объектом, это ссылка на переменную, содержащую ссылку на объект. –

+0

@BenVoigt - Я имею в виду, что его можно рассматривать как один объект, а не только для одного объекта. Обратите внимание на то, что я сказал о «псевдониме». Если они хотят получить все подробные подробные данные, они могут увидеть часть спецификации, на которую я ссылаюсь. –

+0

Кроме того, нет смысла говорить, что «переменные всегда передаются по значению». «Передача по значению» означает передачу параметра, т. Е. Аргумент, а не переменную. –

2

Эти две концепции не совпадают. Параметр метода может быть изменен с помощью ref независимо от того, является ли он типом значения или ссылочным типом.

Передача типа по ссылке позволяет вызываемому методу изменять объект, на который ссылается параметр, или изменить местоположение хранилища параметра.

static void Main() 
{ 
    Foo item = new Foo("aaa"); 
    GetByReference(ref item); 
    Console.WriteLine(item.Name) 
} 
static void ChangeByReference(ref Foo itemRef) 
{ 
    itemRef = new Foo("bbb"); 
} 

это будет на самом деле печатать «В», потому что в этом случае вы не изменили значение объектов, но вы изменили сам объект

2

При передаче объекта в качестве параметра метода, вы передаете новый указатель, который ссылается на исходный объект. Если вы передаете объект как параметр ref, вы передаете тот же указатель, который использует метод вызывающего.Пример:

public void F(ref A a, A b){ 
    a = new A(1); 
    b.Property = 12; 
    b = new B(2); 
} 

public void G(){ 
    A a = new A(0); 
    A b = new A(0); 
    F(a,b); 
    System.Console.WriteLine(a + " - " + b); 
} 

Выходной сигнал 1-12, поскольку указатель объекта b не изменяется, но исходный объект изменяется.

3

ref просто создает ссылку на исходное значение. С ссылаются на типы, которые «значение» - это местоположение памяти переменной. Когда вы используете ref, метод теперь может изменить исходную ссылку на переменную. Если вы затем сделаете то же самое с аргументом, который уже равен ref, второй метод просто имеет ту же ссылку, что и первый метод.

+0

простой и понятный –

4

В вызове, как

F(ref a); // ByRef parameter 

переменной a «используемые непосредственно» тело методы F. Там только одно место хранения. Если метод F присваивает его параметру, это присвоение будет отображаться всем, кто может видеть a, немедленно. И наоборот, если кто-то (за пределами F) присваивает a, тогда как работает метод F, тогда параметр F изменится на новый объект «внезапно».

С другой стороны, в вызове, как

F(a); // normal value parameter 

переменная a сначала копируется в новую переменную, а затем новая переменная используется внутри F.Теперь, если тип параметра F тип значения (как struct или enum), то копии делаются по значению. Таким образом, копируются все данные. Но если тип параметра является ссылочного типа (class (включая тип массива), interface, delegate), копия a включает копию из ссылки только.

Чтобы проверить понимание случае параметра значение с параметром в class типа, выяснить, что делают эти методы:

static void F1(List<int> list>) // no ref modifier 
{ 
    list.Clear(); 
} 

static void F2(List<int> list>) // no ref modifier 
{ 
    list = new List<int>(); 
} 

Вот некоторые, возможно, интересный пример с ref:

static void G(ref string a, ref string b) 
{ 
    if (string.Equals(a, b)) 
     b += "_unique"; 

    // Is it now safe to say that a and b are distinct? 
    // No. Anyone could have changed either a or b by now. 
    // For example, a and b could "alias" public fields visisble to other threads. 
} 

В качестве пример использования G выше, рассмотрите код var x = "initial"; G(ref x, ref x);, и в этом случае a изменится вместе с b внутри метода G.