C#ref关键字用法

我理解(或者至少我相信我这样做)将一个类的实例传递给一个方法,而不是通过ref传递一个方法。 何时或在什么情况下应该通过ref传递类实例? 在为类实例使用ref关键字时,是否有最佳实践?

我曾经遇到过输出和参数参数的最清楚的解释是…… Jon Skeet。

参数在C#中传递

他没有进入“最佳实践”,但如果你理解他给出的例子,你就会知道什么时候需要使用它们。

如果您可以替换原始对象,则应将其作为ref发送给他。 如果它只是用于输出并且在调用函数之前可以未初始化 ,那么你将out

简而言之,如果您希望调用的函数能够更改该变量的值,则可以将值作为ref参数传递。

这与将引用类型作为参数传递给函数不同。 在这些情况下,您仍然按值传递,但该值参考。 在传递ref的情况下,然后发送对变量的实际引用; 实质上,你和你正在调用的函数“共享”相同的变量。

考虑以下:

 public void Foo(ref int bar) { bar = 5; } ... int baz = 2; Foo(ref baz); 

在这种情况下, baz变量的值为5,因为它是通过引用传递的。 语义对于值类型是完全清楚的,但对于引用类型不是很清楚。

 public class MyClass { public int PropName { get; set; } } public void Foo(MyClass bar) { bar.PropName = 5; } ... MyClass baz = new MyClass(); baz.PropName = 2; Foo(baz); 

正如预期的那样, baz.PropName将为5,因为MyClass是一个引用类型。 但是,我们这样做:

 public void Foo(MyClass bar) { bar = new MyClass(); bar.PropName = 5; } 

使用相同的调用代码, baz.PropName将保持为2.这是因为即使MyClass是引用类型, Foo也有自己的bar变量; barbaz刚开始时的值相同,但一旦Foo分配了一个新值,它们只是两个不同的变量。 但是,如果我们这样做:

 public void Foo(ref MyClass bar) { bar = new MyClass(); bar.PropName = 5; } ... MyClass baz = new MyClass(); baz.PropName = 2; Foo(ref baz); 

我们最终将PropName 5,因为我们通过引用传递了baz ,使得两个函数“共享”相同的变量。

ref关键字允许您通过引用传递参数。 对于引用类型,这意味着传递对对象的实际引用(而不是该引用的副本)。 对于值类型,这意味着传递对包含该类型值的变量的引用。

这用于需要返回多个结果但不返回复杂类型以封装这些结果的方法。 它允许您将对象的引用传递给方法,以便该方法可以修改该对象。

要记住的重要一点是,引用类型通常不通过引用传递,传递引用的副本。 这意味着您没有使用传递给您的实际引用。 当您在类实例上使用ref ,您正在传递实际引用本身,因此对它的所有修改(例如将其设置为null )将应用于原始引用。

将引用类型(非值类型)传递给方法时,在两种情况下都只传递引用。 但是当您使用ref关键字时,被调用的方法可以更改引用。

例如:

 public void MyMethod(ref MyClass obj) { obj = new MyClass(); } 

别处:

 MyClass x = y; // y is an instance of MyClass // x points to y MyMethod(ref x); // x points to a new instance of MyClass 

当调用MyMethod(ref x)时 ,x将在方法调用后指向新创建的对象。 x不再指向原始对象。

大多数通过引用传递引用变量的用例涉及初始化和out比ref更合适。 并且它们编译为相同的东西(编译器强制执行不同的约束 – 在传入之前初始化ref变量并且在方法中初始化out变量)。 因此,我能想到的唯一一个有用的地方就是你需要对实例化的ref变量进行一些检查,并且可能需要在某些情况下重新初始化。

这可能也是修改Asaf R.指出的不可变类(如字符串)所必需的。

我发现使用ref关键字很容易遇到麻烦。

即使没有方法签名中的ref关键字,以下方法也会修改f,因为f是引用类型:

 public void TrySet(Foo f,string s) { f.Bar = s; } 

然而,在第二种情况下,原始Foo仅受第一行代码的影响,方法的其余部分以某种方式创建并仅影响新的局部变量。

 public void TryNew(Foo f, string s) { f.Bar = ""; //original f is modified f = new Foo(); //new f is created f.Bar = s; //new f is modified, no effect on original f } 

如果编译器在这种情况下给你一个警告会很好。 基本上你正在做的是将你收到的引用替换为另一个引用不同内存区域的引用。

它实际上是想用新实例替换对象,使用ref关键字:

 public void TryNew(ref Foo f, string s)... 

但是你不是在脚下射击吗? 如果调用者不知道创建了新对象,则以下代码可能无法按预期工作:

 Foo f = SomeClass.AFoo; TryNew(ref f, "some string"); //this will clear SomeClass.AFoo.Bar and then create a new distinct object 

如果您尝试通过添加行来“修复”问题:

 SomeClass.AFoo = f; 

如果代码在其他地方拥有对SomeClass.AFoo的引用,那么该引用将变为无效…

作为一般规则,您可能应该避免使用new关键字来更改从另一个类读取的对象或作为方法中的参数接收的对象。

关于ref关键字与引用类型的使用,我可以建议这种方法:

1)如果只是设置引用类型的值但是在函数或参数名称和注释中显式,则不要使用它:

 public void SetFoo(Foo fooToSet, string s) { fooToSet.Bar = s; } 

2)如果有合理的理由用新的不同实例替换输入参数,请使用带有返回值的函数:

 public Foo TryNew(string s) { Foo f = new Foo(); f.Bar = s; return f; } 

但是使用此函数可能仍会对SomeClass.AFoo场景产生不良后果:

 SomeClass.AFoo = TryNew("some string");//stores a different object in SomeClass.AFoo 

3)在某些情况下,例如字符串交换示例,使用ref params非常方便,但就像在情况2中一样,确保交换对象地址不会影响代码的其余部分。

因为它为你管理内存分配,所以C#很容易忘记内存管理的一切,但它确实有助于理解指针和引用的工作原理。 否则,您可能会引入难以发现的细微错误。

最后,通常情况下,人们会想要使用类似memcpy的函数,但我知道C#中没有这样的东西。