我理解(或者至少我相信我这样做)将类的实例传递给ref
方法而不传递ref
意味着什么。何时或在什么情况下应该通过ref
传递类实例?在为类实例使用ref
关键字时是否有最佳做法?
答案 0 :(得分:18)
我曾经遇到过输出和参数参数的最清楚的解释是...... Jon Skeet's。
他没有进入“最佳实践”,但如果你理解他给出的例子,你就会知道什么时候需要使用它们。
答案 1 :(得分:11)
如果替换原始对象,则应将其作为ref
发送给他。如果它仅用于输出,并且在调用函数之前可以未初始化,则您将使用out
。
答案 2 :(得分:6)
简洁地说,如果您希望调用的函数能够更改该变量的值,则可以将值作为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
的自己的变量; bar
和baz
只是以相同的值开头,但一旦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
,使这两个函数“共享”相同的变量。
答案 3 :(得分:2)
ref
关键字允许您通过引用传递参数。对于引用类型,这意味着传递对象的实际引用(而不是该引用的副本)。对于值类型,这意味着传递对包含该类型值的变量的引用。
这用于需要返回多个结果但不返回复杂类型以封装这些结果的方法。它允许您将对象的引用传递给方法,以便该方法可以修改该对象。
重要的是要记住,引用类型通常不通过引用传递,传递引用的副本。这意味着您没有使用传递给您的实际引用。当您在类实例上使用ref
时,您将传递实际引用本身,因此对它的所有修改(例如将其设置为null
)将应用于原始引用。
答案 4 :(得分:0)
将引用类型(非值类型)传递给方法时,在两种情况下都只传递引用。但是当您使用 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 不再指向原始对象。
答案 5 :(得分:0)
通过引用传递引用变量的大多数用例涉及初始化和out比ref更合适。并且它们编译为相同的东西(编译器强制执行不同的约束 - 在传入之前初始化ref变量,并且在方法中初始化out变量)。因此,我能想到的唯一一个有用的地方就是你需要检查一个实例化的ref变量,并且可能需要在某些情况下重新初始化。
这可能也是修改Asaf R指出的不可变类(如字符串)所必需的。
答案 6 :(得分:0)
我发现使用ref关键字很容易遇到麻烦。
以下方法将修改f,即使方法签名中没有ref关键字,因为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)在某些情况下,例如字符串交换示例here,使用ref params非常方便,但就像在情况2中一样,确保交换对象地址不会影响代码的其余部分。 / p>
因为它为你管理内存分配,所以C#很容易忘记内存管理的一切,但它确实有助于理解指针和引用的工作原理。否则你可能会引入难以发现的细微错误。
最后,通常情况下,人们会想要使用类似memcpy的函数,但我知道C#中没有这样的东西。