使用ref与C类#

时间:2009-06-09 16:34:58

标签: c# pointers reference keyword

我想给我正在制作的课程提供一个链接列表。我希望该类写入该列表(例如,通过.addLast())。

我应该使用ref关键字吗?

我对在C#中使用refout关键字的位置感到有些困惑,因为所有都是在堆上动态分配的,我们实际上使用了大多数指针操作。
当然,outref关键字对基元和结构有意义。

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

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

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

7 个答案:

答案 0 :(得分:29)

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

Jon Skeet编写了an excellent article关于C#中的参数传递,它比较和对比了值类型,引用类型,按值传递,通过引用传递(ref)和输出参数({{1 }})。我建议你花点时间仔细阅读这些内容,你的理解应该变得更加清晰。

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

值参数:

  

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

参考参数:

  

参考参数不通过   中使用的变量的值   函数成员调用 - 他们使用   变量本身。而不是   为...创建新的存储位置   函数成员中的变量   声明,相同的存储位置   使用,所以变量的值   在函数成员和值中   参考参数将始终   是相同的。参考参数需要   ref修饰符作为两者的一部分   声明和调用 - 那   意味着你什么时候总是很清楚   通过引用传递的东西。让我们   看看我们之前的例子   将参数更改为a   参考参数:

总结:在阅读了我的回复和Jon Skeet的文章之后,我希望您会看到无需任何在您的问题的上下文中使用out关键字

答案 1 :(得分:15)

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

答案 2 :(得分:9)

您需要在引用类型中使用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");
}

答案 3 :(得分:2)

不,你不需要使用参考。

LinkedList是一个对象,因此它已经是一个引用类型。参数list是对LinkedList对象的引用。

有关值类型的说明,请参阅此MSDN article。值类型通常是您使用refout关键字的参数。

您可能还希望按ref传递引用类型。这将允许您将引用指向另一个对象。

每次传递object o时,您实际上都是在传递对象的引用。当你传递`ref object o'时,你传递了对引用的引用。这允许您修改引用。

Passing Reference-Type Parameters也可以帮助您理解。

答案 4 :(得分: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
}

答案 5 :(得分: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);

如前所述,请阅读this详细说明。它还解释了字符串。

有趣的是,通过引用调用使用底层指针,所以我们得到这个代码:

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

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

答案 6 :(得分:1)

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

在这个例子中你不需要使用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
  7. 这允许像这样的一些奇怪:

    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的例子是struct而不是classStruct是类似int的值类型,必须以相同的方式处理。