前几天有人问我他们什么时候应该使用参数关键字out而不是ref。虽然我(我认为)理解ref和out关键字之间的区别(之前已经问过了),最好的解释似乎是ref == in和out,有什么(假设或代码)的例子,我应该总是使用out而不是ref。
既然ref更通用,为什么还要用out呢?它只是语法上的糖吗?
前几天有人问我他们什么时候应该使用参数关键字out而不是ref。虽然我(我认为)理解ref和out关键字之间的区别(之前已经问过了),最好的解释似乎是ref == in和out,有什么(假设或代码)的例子,我应该总是使用out而不是ref。
既然ref更通用,为什么还要用out呢?它只是语法上的糖吗?
当前回答
关于c# 7的额外注意事项: 在c# 7中,不需要使用out预先声明变量。代码是这样的:
public void PrintCoordinates(Point p)
{
int x, y; // have to "predeclare"
p.GetCoordinates(out x, out y);
WriteLine($"({x}, {y})");
}
可以这样写:
public void PrintCoordinates(Point p)
{
p.GetCoordinates(out int x, out int y);
WriteLine($"({x}, {y})");
}
来源:c# 7的新特性。
其他回答
你是正确的,在语义上,ref同时提供“in”和“out”功能,而out只提供“out”功能。有一些事情需要考虑:
out requires that the method accepting the parameter MUST, at some point before returning, assign a value to the variable. You find this pattern in some of the key/value data storage classes like Dictionary<K,V>, where you have functions like TryGetValue. This function takes an out parameter that holds what the value will be if retrieved. It wouldn't make sense for the caller to pass a value into this function, so out is used to guarantee that some value will be in the variable after the call, even if it isn't "real" data (in the case of TryGetValue where the key isn't present). out and ref parameters are marshaled differently when dealing with interop code
Also, as an aside, it's important to note that while reference types and value types differ in the nature of their value, every variable in your application points to a location of memory that holds a value, even for reference types. It just happens that, with reference types, the value contained in that location of memory is another memory location. When you pass values to a function (or do any other variable assignment), the value of that variable is copied into the other variable. For value types, that means that the entire content of the type is copied. For reference types, that means that the memory location is copied. Either way, it does create a copy of the data contained in the variable. The only real relevance that this holds deals with assignment semantics; when assigning a variable or passing by value (the default), when a new assignment is made to the original (or new) variable, it does not affect the other variable. In the case of reference types, yes, changes made to the instance are available on both sides, but that's because the actual variable is just a pointer to another memory location; the content of the variable--the memory location--didn't actually change.
与ref关键字一起传递表示原始变量和函数参数实际上都指向相同的内存位置。同样,这只影响赋值语义。如果将一个新值赋给其中一个变量,那么由于其他变量指向相同的内存位置,新值将反映在另一侧。
如果计划读取和写入参数,则需要使用ref。如果你只打算写,你需要使用out。实际上,当您需要多个返回值时,或者当您不想使用正常的返回机制进行输出时(但这种情况应该很少发生),就会使用out。
有一些语言机制可以帮助这些用例。Ref形参在传递给方法之前必须已经初始化(强调它们是可读可写的),out形参在被赋值之前不能被读取,并且保证在方法结束时已经被写入(强调它们只能被写入)。违反这些原则将导致编译时错误。
int x;
Foo(ref x); // error: x is uninitialized
void Bar(out int x) {} // error: x was not written to
例如,int。TryParse返回bool类型,并接受out int形参:
int value;
if (int.TryParse(numericString, out value))
{
/* numericString was parsed into value, now do stuff */
}
else
{
/* numericString couldn't be parsed */
}
这是需要输出两个值的一个明确示例:数值结果和转换是否成功。CLR的作者决定在这里选择out,因为他们不关心int之前可能是什么。
作为参考,你可以看联锁。增量:
int x = 4;
Interlocked.Increment(ref x);
联锁。Increment自动地增加x的值。因为需要读取x来增加它,所以在这种情况下ref更合适。你完全关心x在传递给Increment之前是什么。
在c#的下一个版本中,甚至可以声明变量In out形参,更加强调它们只输出的性质:
if (int.TryParse(numericString, out int value))
{
// 'value' exists and was declared in the `if` statement
}
else
{
// conversion didn't work, 'value' doesn't exist here
}
你为什么要用out?
让其他人知道当变量从被调用的方法返回时将被初始化!
如上所述: 对于out形参,调用方法需要在方法返回之前赋值。
例子:
Car car;
SetUpCar(out car);
car.drive(); // You know car is initialized.
你应该使用out,除非你需要参考。
当数据需要被编组到另一个进程时,它会产生很大的不同,这可能会花费很高。因此,您希望避免在方法不使用初始值时编组初始值。
除此之外,它还向声明或调用的读者显示初始值是相关的(并可能被保留),还是被丢弃。
作为一个微小的区别,out形参不需要初始化。
out的示例:
string a, b;
person.GetBothNames(out a, out b);
其中GetBothNames是一个原子检索两个值的方法,无论a和b是什么,该方法都不会改变行为。如果调用到夏威夷的服务器,将初始值从这里复制到夏威夷是浪费带宽。使用ref的类似代码段:
string a = String.Empty, b = String.Empty;
person.GetBothNames(ref a, ref b);
可能会让读者感到困惑,因为看起来a和b的初始值是相关的(尽管方法名表明它们不是)。
引用的例子:
string name = textbox.Text;
bool didModify = validator.SuggestValidName(ref name);
这里的初始值与方法相关。
Out是ref的约束版本。
在方法体中,需要在离开方法之前为所有out参数赋值。 另外,分配给out形参的值将被忽略,而ref要求分配这些值。
out允许你这样做:
int a, b, c = foo(out a, out b);
其中ref需要分配a和b。