在 C# 函数参数中提及 ref
本文关键字:ref 参数 函数 | 更新日期: 2023-09-27 18:28:12
我做了一个函数,我要在其中传递字符串数组,就像string[] aa= null
一样在函数定义中,我对它进行了一些更新,然后我发现在执行/返回函数时它的值没有更新。我无法理解 C# 中的哪些内容需要提及 ref 关键字。它不是总是通过引用传递参数为什么需要提及 ref 吗?哪些对象需要用 ref 提及才能通过引用传递?
代码是这样的,只是想显示我在做什么,我无法使用 ref 更新值。
string[] temp = null
foo(ref temp);
//function definition
void foo (ref string[] temp)
{
temp = {"Hello World ","You must be updated now"}
}
foreach(string s in temp)
System.Console.WriteLine(s)
你对错综复杂的问题有点误解。如果您传递一个引用对象,那么它按值传递引用(继续阅读,希望它应该开始更有意义(。但是,这并不意味着它是通过 ref 传递的。实际上,默认情况下,C# 参数是按值传递的。
这是一篇很好的文章,可以很好地解释这一点
而且,当我说您按值传递引用时,这是解释我的意思的片段
在 C# 中,参数(默认情况下(按值传递,这意味着 它们在传递给方法时被隐式复制。 对于值类型 参数,这意味着物理复制实例(在同一 复制方式 P2(,而对于引用类型,它意味着复制 参考(以与复制 F2 的方式相同(
.但是,在您的情况下,您正在传入对象,然后在方法中使用新引用创建一个新对象。因此,原始引用保持不变,您可以通过执行更新而不是全新的创建来查看这一点。当您显式说 ref 时,您现在正在传递对引用的引用,因此它之所以有效,是因为您只使用指向引用的指针,并且当您创建新对象时,它将被放置在该引用位置。
正如 eouw0o83hf 所提到的,如果要创建一个全新的对象,那么您应该使用out
来表示这一点。 ref
通常更多地用于值对象,这些对象不是通过引用传递的。
总结一下:
- 如果它是一个值类型,并且您希望更新值并使其反映在任何地方,则需要使用
ref
- 如果是引用类型
- 如果您希望更新值以使其反映到任何地方,那么您可以正常传入(无
ref
或out
( - 如果您希望在方法中创建一个全新的实例并对其进行反映,则应使用
out
- 如果您希望更新值以使其反映到任何地方,那么您可以正常传入(无
更新
下面是一篇 MSDN 文章,确切地解释了您要问的内容:)
这是因为您实际上返回了一个全新的对象,而不是修改现有对象的条目。如果要分配新对象,则应改用out
。
下面是一个示例,显示了 out
、ref
和常规传递如何在数组参数上工作。如您所见,无论是否指定ref
,数组都是通过引用传递的;但是,如果返回一个全新的对象,则需要指定out
:
class Program
{
static void Main(string[] args)
{
string[] val;
foo(out val);
Console.WriteLine(string.Join(",", val));
// Output: 1, 2
bar(ref val);
Console.WriteLine(string.Join(",", val));
// Output: modified, 2
bar2(val);
Console.WriteLine(string.Join(",", val));
// Output: modified again, 2
Console.Read();
}
static void foo(out string[] temp)
{
temp = new string[]{"1", "2"};
}
static void bar(ref string[] temp)
{
temp[0] = "modified";
}
static void bar2(string[] temp)
{
temp[0] = "modified again";
}
}
# 始终按值传递参数,除非使用 ref
或 out
修饰符。
string[] temp = { "zero", "one", "two" };
MutateByVal(temp);
MutateByRef(ref temp);
void MutateByVal(string[] arr)
{
// arr and temp are separate references to the same array
// the value of arr (a reference) is a copy of the value of temp
// mutate the array referenced by arr
arr[1] = "mutated!";
// arr and temp still point at the same array
// so both arr and temp now contain { "zero", "mutated!", "two" }
// re-assign arr
arr = new[] { "blah", "blah", "blah" };
// arr and temp now point at different arrays
// arr now contains { "blah", "blah", "blah" }
// temp still contains { "zero", "mutated!", "two" }
}
void MutateByRef(ref string[] arr)
{
// arr is an alias for temp
// that is, they are two different names for the same reference
// mutate the array referenced by arr
arr[1] = "mutated!";
// arr and temp are the same reference
// so both arr and temp now contain { "zero", "mutated!", "two" }
// re-assign arr
arr = new[] { "blah", "blah", "blah" };
// arr and temp are the same reference
// so both arr and temp now contain { "blah", "blah", "blah" }
}
首先,你的伪代码应该可以工作。 但是,在我们开始之前,这里有三件事在起作用:值类型、引用类型和"ref"关键字。
值类型通常是简单的基本类型,如 int、double 等。 字符串是一个奇怪的字符串,因为它被认为是一种值类型。
更复杂的类型(如数组和类(是引用类型。
当您传递 ints 和 double 等值类型时,您将传递值的副本,因此,如果您将 int x = 10 传递到方法中,则一旦离开该方法,该方法中对 x 的更改将不会反映。另一方面,如果传递 MyClass class1,则对 class1 中属性的任何更改都将反映在函数外部。只是不要尝试在方法中更新一个新的 class1,因为它不会在调用方之外更改。
如果要更改方法中的值类型,请按 ref 传递。如果你想更新一个新的类或数组,那么你通过 ref 传递。
还有一件事:使用out和ref之间并不是那么黑白分明。 仅当方法的设计始终仅在方法内部创建类或数组时,才会使用 out。如果要允许创建新对象的可能性,则可以在引用类型上使用 ref。 喜欢
//function definition
void foo (ref string[] temp)
{
if(temp == null)
{
temp = new string[] { "Hello World ", "You must be updated now" };
}
else
{
// do something with the existing temp
}
}
最后,如果这是您的实际代码:
string[] temp = null;
foo(ref temp);
foreach (string s in temp)
System.Console.WriteLine(s);
后:
//function definition
void foo (ref string[] temp)
{
temp = new string[] { "Hello World ", "You must be updated now" };
}
然后,它应该实际工作。
类引用视为"对象 ID"。 如果一个人有一个类类型Car
的变量MyCar
,这个变量实际上并不容纳一辆汽车。 相反,它保存着实际上存储在其他地方的汽车的"对象ID"。 如果MyCar
持有"#1543",则语句MyCar.Color = CarColors.Purple;
实际上不会修改变量MyCar
。 相反,它会告诉系统"对象 #1543 应该是Car
。 将其Color
属性设置为CarColors.Purple." In many cases, a routine which passes a variable of type
汽车will simply want the called code to do something with the
汽车identified by that variable. In a few cases, however, one may be necessary to let the called code change the object ID stored within
我的汽车itself, so that it points to an entirely different instance of
汽车"。
在您的特定情况下,有问题的对象是一个数组。 被调用的例程创建一个全新的数组,但调用方以等于 null 的temp
开头。 调用方看到新数组的唯一方法是将对它的引用存储到 temp
中。 否则,调用方将继续看到temp
之前的任何数组(或null
,如果没有存储数组引用(。
在 C# 中作为参数传递的任何类型都是按值传递的。因此,如果这是一个字符串数组,C# 会复制引用(这是一种引用类型(并将其传入。但由于这是一个引用类型,因此两个变量(来自内部方法范围和外部参数(都将指向托管堆(或大型对象堆(中的同一对象。
代码的问题在于您创建了一个新变量(语法不正确(。要解决此问题,您只需直接使用数组的索引器,即:
void foo (string[] temp) // create a copy of a reference to the string array
{
temp[0] = "Boom"; // temp still points to the same object
}
-------------
string[] temp = new [] {"one", "two", "three"}; //outer variable
foo(temp); // behind the scene we have two variables pointing to the same array
foreach (string s in temp)
System.Console.WriteLine(s);
如果没有ref
,对数组的引用只是简单地复制并传递给方法(按值 - 因为引用类型的值是对对象的引用(;允许方法访问同一个数组,但不允许它修改调用方自己的引用。
这就是ref
(或实际上是out
(关键字的用途。
但是 - 如果您只是希望该方法更改引用的对象,则不需要使用 ref
(取决于构造后是否可以更改类型 - 即它是否不可变(。
所以这个方法将覆盖传递的数组的第一个元素:
public static void foo(string[] ss)
{
if(ss!=null && ss.Length > 0)
ss[0] = "Overwritten";
}
public static void main()
{
var strings = new[] { String.Empty, "Original" };
foo(strings);
Console.WriteLine(strings[0]); //will print 'Overwritten'.
}
但是,foo
在上面的代码中无法做的是new
ss
参数,并期望更改引用的值strings
作为参数从main
传递。
为此,我们需要传递对本地strings
的引用,这就是ref
的用武之地。 如果我们这样做 - 那么main()
中的strings
可以更改,如果传递 null,则指向一个新数组:
public static void foo(ref string[] ss)
{
if(ss==null || ss.Length == 0)
ss= new string[1];
ss[0] = "Overwritten";
}
public static void main()
{
string[] strings = null
foo(ref strings);
Console.WriteLine(strings[0]); //will still print 'Overwritten'.
}