2010-05-20 2 views
2

Я использовал Java, C++, .Net. (в этой последовательности). Когда меня спрашивали о побочных значениях по сравнению с анкерами на интервью, я всегда хорошо справлялся с этим вопросом ... возможно, потому что никто не углублялся в него. Теперь я знаю, что я не вижу всей картины.Если аргумент передается по ссылке в этом примере .net?

Я смотрел на этот раздел кода, написанного кем-то другим:

XmlDocument doc = new XmlDocument(); 
AppendX(doc); // Real name of the function is different 
AppendY(doc); // ditto 

Когда я увидел этот код, я подумал: погоди, я не должен использовать ref перед doc переменной (и изменить AppendX/Y соответственно? Он работает так, как написано, но поставил вопрос, действительно ли я понимаю ключевое слово ref в C#.

Как я думал об этом больше, я вспомнил ранние Java-дни (вступительный язык колледжа). в некотором коде, который я написал, и у него был умственный b замок - он все время спрашивал меня, какие вещи передаются по ссылке и когда по стоимости. Мой неосведомленный ответ был чем-то вроде: Чувак, на Java есть только один вид перехода, и я забыл, какой из них есть :). Чил, не переусердствуйте и просто закодируйте.

Java все еще не имеет ref, не так ли? Тем не менее, хакеры Java кажутся продуктивными. Во всяком случае, кодирование на C++ разоблачило меня по всему эталонному делу, и теперь я смущен.

Следует ли использовать ref в примере выше?

Я предполагаю, что когда ref применяется к типам значений: примитивам, перечислениям, структурам (есть ли что-нибудь еще в этом списке?), Это имеет большое значение. И ... когда применяется к объектам, это не потому, что это все по ссылке. Если все было так просто, то почему компилятор не ограничил использование ключевого слова ref подмножеством типов.

Что касается объектов, подходит ли ref как комментарий? Ну, я помню, что могут быть проблемы с null, а ref также полезен для инициализации нескольких элементов внутри метода (поскольку вы не можете возвращать несколько вещей с тем же легкостью, что и в Python).

Спасибо.

+1

Добавление параметра 'ref' к методу - это то, что вы должны использовать только тогда, когда оно вам нужно. По умолчанию вы должны избегать параметров 'ref'. Если он работает так, как написано, не добавляйте 'ref'. В стороне, часто можно избежать отказа от ref, так как следующие два фрагмента, как правило, будут давать одинаковые результаты (учитывая правильные реализации 'foo'):' foo (ref x); 'и' x = foo (х); '. Лично, когда я даю выбор, я буду реализовывать foo на втором пути. Поэтому неудивительно, что Java-программисты могут выжить без него. – Brian

+0

@Brian, когда у вас есть 'x = foo (x);', ваш 'x' также может быть неизменным (например,' string'). Теперь ... когда один метод должен «возвращать» несколько переменных, и лучше всего их вычислить, потому что они связаны ... тогда вы хотели бы использовать ref или все еще нет? Изменяется ли ответ в зависимости от того, являются ли аргументы неизменными или нет? Думаю, так и будет. –

+1

Если вам нужно вернуть несколько переменных, вы можете использовать 'ref' для этого. Альтернативой является возвращение экземпляра класса, который содержит все материалы, которые вы хотите вернуть. Это часто, но не всегда, лучше. Имейте в виду, что вы также можете использовать параметр 'out' вместо' ref'. Если вы хотите возвратить несколько переменных, но на самом деле их не нужно передавать, «out» лучше. Например, 'foo (out x);' как 'foo (ref x);', но должен использоваться с неинициализированным x. – Brian

ответ

4

Типы объектов всегда передаются по ссылке (фактически эта ссылка передается по значению). Интегральные типы могут передаваться по ссылке или значению.

+2

Сколированный бит - правильный, но бит до него нет. –

+0

@ Jon Skeet: Это то же самое, не так ли? Если вы передаете ссылку на объект, то логически вы передаете объект по ссылке. 'ref' передает * переменную * по ссылке, а не объект, на который ссылается переменная. (Для записи у меня есть точное представление о том, что использует 'ref', и о проходящей семантике, когда не используется' ref'. Мне просто интересно узнать вашу терминологию.) – Joren

+0

@ Joren: Нет, передача по ссылке isn То же самое, что и передача ссылки. Семантика «Pass by reference» подразумевает, что изменения, внесенные в сам параметр, приводятся к аргументу. Теперь в этом случае говорить о передаваемом объекте не так, чтобы начать с него, потому что в Java и C# нет выражения, тип которого * фактически * объект - это * только * когда-либо ссылка. Я нашел намного легче объяснить эту тему, не используя идею передачи фактического * объекта * вообще. –

2

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

3

Метод, который принимает переменную ссылочного типа, переданную ref, может изменить то, на что указывает переменная, а не просто модифицировать ее.

+1

Для однострочного, это очень хорошо. –

2

Это зависит от того, что вы хотите делать AppendX. Если он изменяет содержимое объекта, его не нужно передавать по ссылке ref. Если вы хотите, чтобы AppendX смог изменить объект, на который указывает переменная «doc», вам нужно. «doc» уже является «ссылочным типом», что эквивалентно тому, что он является указателем на объект в C++.

10

Вы не одиноки в смущении.

По умолчанию все передано по значению в C#, но в случае ссылочного типа (например, XmlDocument) это значение является ссылкой.

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

См. Мои article on parameter passing для получения более подробной информации.

+0

Я знаю, что вам нравится обратная связь, поэтому я думал, что ваша статья хороша, но она «песочница» для .Net (и Java, я думаю) мира. Другая статья делает ссылки на C++, открывает банку червей, и именно это я искал с этим конкретным вопросом. Прежде чем подумать, что это ваша вина, подумайте об этом: Ричард Фейнман, яркий парень, который много читал лекции и увлекался обучением и объяснением, признал, что нет единого метода обучения, потому что ученик настолько разнообразен, как они думают и поглощают , То, что сработало для его сына, мало помогло его дочери. Жаль, что я не могу найти ссылку. –

+0

@Hamish: Да, моя статья очень преднамеренно специфична для .NET. Как вы говорите, на других языках есть много червей червей. С точки зрения попытки научить кого-то поведению C#, я считаю, что лучше оставить эти банки закрытыми :) –

1

Я спорил об этом с некоторыми коллегами и в конечном итоге потерял; вот как я научился делать что-то по-своему.

Значения ref и out означают «Я могу/заменит текущую ссылку новой ссылкой». После вызова метода переменная может ссылаться на совершенно другой объект. С другой стороны, всегда известно, что для ссылочных типов свойства этого типа могут измениться, и если они важны, вы должны их кэшировать.

Я не согласен с этим, но, безусловно, имеет смысл иметь что-то, что означает «ссылка изменится».

1

Вы не одиноки в том, что меня путают и кодируют в C++ и C#. Я могу понять, почему (не как критичность, а как комментарий, так как это мир, в котором я живу тоже) в C++, когда вы используете & по аргументу передайте ссылку, в которой вы в основном говорите, что это псевдоним, который я буду использовать внутри метода для аргумента, передаваемого методу. Anything, который вы делаете с этим аргументом, будет иметь такой же эффект, как если бы вы использовали переменную it self. поэтому в коде вы можете сделать: void Foo (MyClass & arg) { arg = новый MyClass (1); }

int x = new MyClass(0); 
Foo(x); 

или

int x = new MyClass(0); 
void Foo() 
{ 
    x = new MyClass(1); 
} 

в любом случае х теперь равен MyClass (1) (и у вас есть утечка Потому что нет никакого способа, вы можете получить к оригиналу, но это не моя точка). Я думаю, что у вас вопрос, который вы уже знали, но он все равно будет служить цели :)

Если вы передаете ссылку в стандарте, эта ссылка передается по значению. Это уже не псевдоним, который вы делаете с объектом, являющимся ссылками, будет воздействовать на объект, но если вы сделаете что-либо для переменной, ссылающейся на объект (например, назначая новый объект), это повлияет только на копию ссылки.Давайте еще немного кода

C#

MyClass x = MyClass(0); 
void Foo(MyClass arg) //reference being passed by val 
{ 
    arg = new MyClass(1); 
} 
Foo(x); 

х по-прежнему равна MyClass (0)

MyClass x = MyClass(0); 
void Foo(ref MyClass arg) //passing by ref 
{ 
    arg = new MyClass(1); 
} 
Foo(ref x); 

х равна MyClass (1)

Таким образом, стандартный аргумент переходящая в C# отличается от передавая ссылку в C++, но используя ключевое слово ref (что не рекомендуется), вы приближаетесь к той же самой механике, что и & в C++ & на C++ обычно рекомендуется из-за оптимизации/отсутствия копирования, как только вы только копируете ссылку на C#, что не вызывает беспокойства, и ref должен использоваться только тогда, когда вам действительно нужен псевдоним для переменной, передаваемой методу aka, когда вы возможно, придется назначать новый экземпляр объекта переменной, а не использовать/изменять состояние объекта.

1

Если вы уже закодировали на C++, вы должны быть знакомы с указателями. Ссылка на объект в коде .NET и Java является указателем под капотом. Он просто явно не написан в синтаксисе, что делает очевидным, что это указатель, вы должны запомнить его. Правило не очень сложно, любая переменная, относящаяся к объекту класса, массиву, строке или System.Object является ссылочным типом, а переменная - указателем. Все остальное - тип значения, а переменная содержит фактическое значение.

Когда вы передаете такую ​​переменную методу, вы передаете значение указателя. Затем этот метод может модифицировать объект «заостренный объект» по своему усмотрению. Передача указателя по ссылке не имеет никакого значения, это все тот же указатель, указывающий на тот же объект.

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

Случай с выбросом - это то, где вы хотите, чтобы вызывающий метод возвращал новый объект. Другими словами, измените значение указателя. Затем вы должны использовать ключевое слово ref, которое создает указатель на указатель. Обычно вы избегаете этого, если метод возвращает объект как возвращаемое значение метода.

+0

Спасибо. Использует ли .NET-инфраструктуру типы значений, которые вписываются в 64-битные, и те, которые не отличаются? Если да, то в чем именно разница? –

+1

Только компилятор JIT заботится. Это зависит от типа процессора, независимо от того, хранит ли он 64-битное значение в регистре CPU. –

1

Многие люди смущены этим. Вот как я думаю об этом. Я не думаю о «ref» как о значении «по ссылке» вообще. Я думаю о «ref» как о значении «alias». То есть, когда вы говорите

void M(ref int x) { x = 123; } 
void N(int z) { ... } 
... 
int y = 456; 
M(ref y); 
N(y); 

, что это «иое у» означает «пожалуйста, соответствующий формальный параметр х в псевдониме к переменным у». То есть, x и y теперь являются переменными для того же места хранения. Когда вы пишете x, вы пишете y, потому что x - другое имя для y.

Когда вы проходите без ref, как в N (y), вы говорите: «y и z - две разные переменные, так что z начинает свое существование с тем же содержимым, что и y».

Как только вы начинаете думать об этом, вам не нужно беспокоиться о пропуске по сравнению с пропущенным значением, бла-бла-бла, все это очень запутывает. Основное различие заключается в том, что нормальная передача создает новую переменную и инициализирует ее аргументом, тогда как ref делает псевдоним существующей переменной.

Хотелось бы, чтобы мы использовали «псевдоним» вместо «ref»; это было бы намного более ясно.

+0

Я предпочитаю ref. Он более совместим с моими существующими знаниями (т. Е. Ref означает, что вы передаете свою ссылку * по ссылке * вместо * по значению *). – Brian

Смежные вопросы