何时传递ref关键字?

10

我已经阅读了传递参数时使用和不使用 ref 的区别,但是什么情况下我会想要使用它们呢?

例如,我有一个方法中的一些逻辑可以重构成自己的方法。Resharper 4.5 将其中一个参数变为了 ref 类型,但如果我手动重构代码,我认为我不需要这样做。

显然,我还存在一些理解上的疑惑。也许举个例子,说明在某些类型或某些编码场景中缺少 ref 关键字会发生什么,会有所帮助吗?

谢谢


1
何时传递 out 参数? - Natrium
@Natrium:我发现时间在你喝了太多之后就过去了 :) - Matthew Whited
6个回答

8

让我将其分解成两个问题:

1)在编写方法时,何时应该使用ref/out形式参数声明?

当您希望您的方法能够读取和写入来自调用者的变量而不仅仅是读取值时,请使用ref/out。

2)为什么“提取方法”重构会产生ref参数?

我不知道Resharper的细节,但我可以猜测。考虑以下邪恶的可变值类型:

struct S 
{ 
  private int x;
  public int X() { return this.x; } 
  public void M() { this.x += 1; } 
}

您有一个方法:

void Foo() 
{
    S s = new S();
    Fred(s);
    Blah(s);
    Bar(s);
    s.M();
    Console.WriteLine(s.X()); // prints 1
}

并且你需要对中间部分进行"提取方法":

void NewMethod(ref S s)
{
    Blah(s);
    Bar(s);
    s.M();
}

void Foo() 
{
    S s = new S();
    Fred(s);
    NewMethod(ref s);
    Console.WriteLine(s.X()); // still prints 1
}

如果您创建了一个没有 "ref" 的方法,那么调用 NewMethod(s) 会将 s 的副本传递给 NewMethod。请记住,值类型是按值复制的;这就是为什么我们称它们为 "值类型"。被改变的将是副本,然后 s.X() 返回零。在重构中引入语义变化是一个不好的想法,而且重构引擎很难知道一个给定的方法是否依赖于值类型的可变性。
这正是为什么应该避免使用可变值类型的另一个原因。

1

我使用引用来进行语义化。考虑一下这种方法:

void AddResultsTable(ref PlaceHolder p) // modifies p; adding a table
{
    var t = new Table();

    AddTableHeader(ref t); // modifies t; adding a table header

    AddTableBody(ref t);  // modifies t; adding a table body

    AddTableFooter(ref t);  // modifies t; adding a table footer

    p.Controls.Add(t);
}

AddResultsTable(ref PlaceHolderResults);

相对于这个:

Table ReturnTable()
{
    var t new Table();

    // AddTableHeader() returns TableHeader
    t.Columns.HeaderColumns.Add(ReturnTableHeader());

    // ... etc.

    return t;
}

PlaceHolder.Controls.Add(ReturnTable());

第一个代码片段对我来说更加清晰简洁;方法修改对象而不是返回新的对象,从而您需要进行添加。所有操作都被封装和隐藏在方法内部。

1

byref 只有在函数需要产生"副作用"时才有意义,即你打算修改值类型参数,或者将另一个对象重新分配给给定的对象参数,并且希望这个更改在函数调用后继续存在。例如:TryGetValue()

否则,最好坚持使用 byval


1
实际上,TryGetValue() 使用 out 关键字,调用方不需要初始化变量,但被调用方需要。ref 关键字用于被调用方可能更改对象引用的情况。 - Yannick Motton

0

从概念上讲,值类型直接存储其值,而引用类型存储对该值的引用。也许你应该重新阅读一下关于引用类型和值类型的知识。

通过引用传递值类型--如上所示--是有用的,但 ref 对于传递引用类型也很有用。这允许被调用的方法修改引用所指向的对象,因为引用本身是按引用传递的。以下示例显示了当引用类型作为 ref 参数传递时,可以更改对象本身。

   class RefRefExample
{
    static void Method(ref string s)
    {
        s = "changed";
    }
    static void Main()
    {
        string str = "original";
        Method(ref str);
        // str is now "changed"
    }
} 

MSDN


0

考虑这个例子:

static int i = 3;

public static void ChangeIntRef(ref int val)
{
   val = 5;
}

public static void ChangeInt(int val)
{
   val = 5;
}

Console.WriteLine(i);
ChangeInt(i);
Console.WriteLine(i);

ChangeIntRef(ref i);
Console.WriteLine(i);

通过将参数传递为ref,您告诉编译器您实际想要传递给方法的是对原始变量的引用。因此,该方法可以更改原始变量的值。
如果您运行上面的代码片段,则结果为:
3
3
5

这应该清楚地表明,如果没有 ref 关键字,ChangeInt 方法无法实际更改原始值。然而,使用 ref 关键字,ChangeIntRef 方法能够更改原始值。


0

这样做的效果是,当控制权返回到调用方法时,对参数所做的任何更改都将反映在该变量中。因此,如果您希望分配给参数的值在方法调用之后仍然存在,则可以考虑使用此功能。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接