используя ссылку с классом C # - PullRequest
16 голосов
/ 09 июня 2009

Я хочу дать определенный связанный список классу, который я делаю. Я хочу, чтобы класс записал в этот список (например, .addLast ()).

Должен ли я использовать ключевое слово ref для этого?

Я несколько озадачен тем, где использовать ключевые слова ref и out в C #, поскольку все классы динамически размещаются в куче, и мы фактически используем указатели для большинства операций. Конечно, ключевые слова out и ref имеют смысл для примитивов и структур.

Кроме того, если я не отправлю список напрямую, а отправлю класс, содержащий список? (это internal и нужно), мне все еще нужно использовать ref? или если я передаю его между функциями, например:

void A(ref LinkedList<int> list){
    B(list);
}

void B(ref LinkedList<int> list){
    _myList = list;
}

Ответы [ 7 ]

29 голосов
/ 09 июня 2009

Это распространенное заблуждение об использовании ключевого слова ref в C #. Его целью является передача либо значения, либо ссылочного типа по ссылке, и вам это нужно только в определенных обстоятельствах, когда вам нужна прямая ссылка на фактический аргумент, а не копия аргумента (будь то значение или сама ссылка) , Крайне важно не путать ссылочные типы с передачей по ссылке в любом случае.

Джон Скит написал отличную статью о передаче параметров в C #, которая сравнивает и сравнивает типы значений, ссылочные типы, передачу по значению, передачу по ссылке (ref) и выходные параметры (out). Я рекомендую вам уделить некоторое время, чтобы прочитать это полностью, и ваше понимание должно стать намного яснее.

Чтобы процитировать наиболее важные части с этой страницы:

Значения параметров:

По умолчанию параметры являются значениями параметры. Это означает, что новый место хранения создано для переменная в члене функции декларация, и начинается с значение, которое вы указываете в вызов члена функции. если ты изменить это значение, это не меняет любые переменные, участвующие в призывание

Справочные параметры:

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

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

15 голосов
/ 09 июня 2009

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

9 голосов
/ 09 июня 2009

Единственный раз, когда вам нужно использовать ref со ссылочным типом, это если вы собираетесь создавать новый объект внутри функции.

Пример # 1 : ref ключевое слово не обязательно.

// ...
   List myList = new List();
   PopulateList(myList);
// ...
void PopulateList(List AList)
{
   AList.Add("Hello");
   AList.Add("World");
}

Пример # 2 : ref необходимо ключевое слово.

// ...
   List myList;
   PopulateList(ref myList);
// ...
void PopulateList(ref List AList)
{
   AList = new List();
   AList.Add("Hello");
   AList.Add("World");
}
2 голосов
/ 10 июня 2009

Я добавляю этот ответ для программистов, которые привыкли к C ++, как и я.

Классы, интерфейсы, делегаты и массивы reference types, что означает, что они имеют базовый указатель. Обычные вызовы функций копируют этот указатель (ссылку) по значению, а при отправке по ссылке отправляет ссылку на эту ссылку:

//C# code:
void Foo(ClassA     input)
void Bar(ClassA ref input)

//equivalent C++ code:
void Foo(ClassA*  input)
void Bar(ClassA*& input)

Примитивы, такие как int, double и т. Д. Структуры и строки (string являются исключением из них, но работают аналогично), размещаются в куче, поэтому все работает немного иначе:

//C# code:
void Foo(StructA     input)
void Bar(StructA ref input)

//equivalent C++ code:
void Foo(StructA  input)
void Bar(StructA& input)

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

//C# code:
void Foobar(ClassB ref input)
...
ClassB instance = new ClassB();
Foobar(ref instance);

//equivalent C++ code:
void Foobar(ClassB*& input)
...
ClassB instance* = new ClassB();
Foobar(instance);

Как уже было сказано, прочитайте это подробное объяснение. Это также объясняет о строках.



Интересно отметить, что вызов по ссылке работает с указателем, лежащим в основе, поэтому мы получаем следующий код:

//C# code:
void Foo(ClassA input){
    input = input + 3;
}
void Bar(ClassA ref input){
    input = input + 3;
}
//equivalent C++ code:
void Foo(ClassA&  input){
    input = input + 3;
}
void Bar(ClassA*&  input){
    *input = *input + 3;
}
//equivalent pure C code:
void Fun(ClassA* input){
    *input = *input + 3;
}
void Fun(ClassA** input){
    *(*input) = *(*input) + 3;
}

это грубый эквивалент, но это несколько верно.

2 голосов
/ 09 июня 2009

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

void methodA(string test)
{
    test = "Hello World";
}

void methodB(ref string test)
{
    test = "Hello World";
}

void Runner()
{
    string first= "string";
    methodA(first);
    string second= "string";
    methodB(ref second);
    Console.WriteLine((first == second).ToString()); //this would print false
}
2 голосов
/ 09 июня 2009

Нет, вам не нужно использовать ref.

LinkedList - это объект, поэтому он уже является ссылочным типом. Параметр list является ссылкой на объект LinkedList.

См. статью MSDN для описания типов значений. Типы значений обычно являются параметрами, с которыми вы бы использовали ключевые слова ref или out.

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

Каждый раз, когда вы передаете object o, вы действительно передаете ссылку на объект. Когда вы передаете `ref object o ', вы передаете ссылку на ссылку. Это позволяет вам изменить ссылку.

Передача параметров эталонного типа также может помочь вам понять.

1 голос
/ 27 января 2018

Я знаю, что это старый вопрос, но ни один из ответов не дает четкой прямой причины, по моему мнению.

Вам не нужно использовать ref в этом случае, и вот почему. Рассмотрим эту функцию:

void Foo(MyClass a1, ref MyClass a2, out MyClass b1, int c1, MyStruct d1, ref MyStruct d2)
{
}

Теперь вызовите эту функцию как

MyClass  a = new MyClass();
MyClass  b = null
int      c = 3;
MyStruct d = new MyStruct();

Foo(a, ref a, b, c, d, ref d);

Вот что вы получаете внутри функции:

void Foo(MyClass a1, ref MyClass a2, 
         out MyClass b1, 
         int c1, 
         MyStruct d1, ref MyStruct d2)
{
   a1 is a copy in memory of the pointer to the instantiated class a;
   a2 is the pointer to the instantiated class a;

   b1 is the pointer to b, but has the additional check of having to be set within this function - and cannot be used before being set;

   c1 is a copy in memory of the variable c;

   d1 is a copy in memory of the struct d;
   d2 is the struct d;
}

Важные вещи для реализации:

  1. настройка a1 на null будет не установить a на null.
  2. настройка a2 на null установит a на null.
  3. требуется установка b1.
  4. настройка c1 будет не изменить c.
  5. настройка d1 будет не изменить d.
  6. значение d2 изменится d.

Это допускает некоторые странности, подобные этой:

void Foo(MyClass x, ref MyClass y)
{
    x = null;
    y.Bar("hi");
}

Называется как:

MyClass a = new MyClass();
Foo(a, ref a);

Вы используете класс, и поэтому ваша ситуация больше похожа на переменную a1 в вызове функции. Что означает ref строго не требуется.

Статья Джона Скита не сильно вам поможет, потому что его пример с IntHolder - это struct, а не class. Struct является типом значения, подобным int, и должен обрабатываться так же.

...