21

我想为我正在制作的课程提供某个链接列表。我希望班级写入该列表(例如通过.addLast())。

我应该ref为此使用关键字吗?

我对在 C# 中在哪里使用refand关键字感到有些困惑,因为所有都是在堆上动态分配的,而且我们实际上对大多数操作都使用指针。 当然,关键字对原语和结构有意义。out
outref

另外,如果我不直接发送列表,而是发送包含列表的类?(它internal需要),我还需要使用ref吗?或者如果我在函数之间传递它,例如:

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

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

7 回答 7

30

ref这是在 C# 中使用关键字的常见误解。它的目的是通过引用传递值或引用类型,并且仅在需要直接引用实际参数而不是参数的副本(无论是值还是引用本身)的特定情况下才需要它. 在任何情况下都不要将引用类型按引用传递相混淆。

Jon Skeet 写了一篇关于 C# 参数传递的优秀文章,其中比较和对比了值类型、引用类型、按值传递、按引用传递 ( ref) 和输出参数 ( out)。我建议您花一些时间完整阅读本文,您的理解应该会变得更加清晰。

引用该页面中最重要的部分:

值参数:

默认情况下,参数是值参数。这意味着在函数成员声明中为变量创建了一个新的存储位置,它以您在函数成员调用中指定的值开始。如果更改该值,则不会更改调用中涉及的任何变量

参考参数:

引用参数不传递函数成员调用中使用的变量的值——它们使用变量本身。而不是在函数成员声明中为变量创建新的存储位置,而是使用相同的存储位置,因此函数成员中变量的值和引用参数的值将始终相同。引用参数需要 ref 修饰符作为声明和调用的一部分 - 这意味着当您通过引用传递某些内容时它总是很清楚。再看我们前面的例子,只是把参数改成引用参数:

总结:在阅读了我的回复和 Jon Skeet 的文章后,我希望你会看到在你的问题的上下文中没有任何必要使用ref关键字。

于 2009-06-09T16:39:34.733 回答
19

对于您正在做的事情,您不需要使用 ref. 如果您确实使用 ref 传递了列表,那么您将允许调用者更改您引用的列表,而不仅仅是更改列表的内容。

于 2009-06-09T16:41:58.320 回答
15

唯一需要将 ref 与引用类型一起使用的情况是,如果您要在函数中创建新对象。

示例#1ref关键字不是必需的。

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

示例#2ref关键字是必需的。

// ...
   List myList;
   PopulateList(ref myList);
// ...
void PopulateList(ref List AList)
{
   AList = new List();
   AList.Add("Hello");
   AList.Add("World");
}
于 2009-06-09T16:55:23.793 回答
3

我知道这是一个老问题,但在我看来,没有一个答案能给出一个很好的直接原因。

您不需要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. 设置a1null不会设置为。_anull
  2. 设置a2null将设置anull
  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不是严格要求的。

Jon Skeet 的文章对您没有太大帮助,因为他的示例IntHolder不是. 是值类型,必须以相同的方式处理。structclassStructint

于 2018-01-27T05:42:41.693 回答
2

不,您不需要使用 ref。

LinkedList 是一个对象,所以它已经是一个引用类型。该参数list是对 LinkedList 对象的引用。

有关值类型的描述,请参阅此MSDN 文章。值类型通常是您将使用reforout关键字的参数。

您可能还想通过 传递引用类型ref。这将允许您将引用指向另一个对象。

任何时候传递 an 时,object o您实际上都是在传递对对象的引用。当你传递一个 `ref object o' 时,你传递的是对引用的引用。这允许您修改参考。

传递引用类型参数也可以帮助您理解。

于 2009-06-09T16:36:22.210 回答
2

在您发布的两个片段中,无需通过 ref 传递列表。引用 Jon Skeet 的话,对象引用是按值传递的。这意味着,当方法将或可能更改对象引用并且您希望此新引用带回调用方法时,您将希望引用引用类型。例如:

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
}
于 2009-06-09T16:47:46.923 回答
2

我正在为像我这样习惯于 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 等结构和字符串的原语(字符串是一个例外,但工作方式类似)在堆上分配,所以事情有点不同:

//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;
}

这是一个粗略的等价物,但它有点真实。

于 2009-06-09T21:26:29.650 回答