c# 4.0可选的out/ref参数

c# 4.0是否允许可选的outref参数?

122413 次浏览
如前所述,这是不允许的,我认为这是非常有意义的。 然而,为了添加更多细节,这里引用c# 4.0规范, section 21.1:

构造函数、方法、索引器和委托类型的形式参数可以声明为可选:

< p >固定参数:< br > ,,attributes选择 parameter-modifier选择类型标识符default-argument选择
默认参数:< br > ,,=表达式

  • 带有默认参数固定参数可选参数,而没有默认参数固定参数所需的参数
  • formal-parameter-list中,必选参数不能出现在可选参数之后。
  • refout参数不能有默认参数

不。

一个解决方法是重载另一个方法,有out / ref参数,它只调用当前方法。

public bool SomeMethod(out string input)
{
...
}


// new overload
public bool SomeMethod()
{
string temp;
return SomeMethod(out temp);
}

如果你有 c# 7.0,你可以简化:

// new overload
public bool SomeMethod()
{
return SomeMethod(out _);    // declare out as an inline discard variable
}

(感谢@奥斯卡/ @赖纳指出这一点。)

不是,但是另一个很好的替代方法是让方法使用一个泛型模板类来处理可选参数,如下所示:

public class OptionalOut<Type>
{
public Type Result { get; set; }
}

那么你可以这样使用它:

public string foo(string value, OptionalOut<int> outResult = null)
{
// .. do something


if (outResult != null) {
outResult.Result = 100;
}


return value;
}


public void bar ()
{
string str = "bar";


string result;
OptionalOut<int> optional = new OptionalOut<int> ();


// example: call without the optional out parameter
result = foo (str);
Console.WriteLine ("Output was {0} with no optional value used", result);


// example: call it with optional parameter
result = foo (str, optional);
Console.WriteLine ("Output was {0} with optional value of {1}", result, optional.Result);


// example: call it with named optional parameter
foo (str, outResult: optional);
Console.WriteLine ("Output was {0} with optional value of {1}", result, optional.Result);
}

实际上有一种方法可以做到这一点,这是c#允许的。这又回到了c++,并且违背了c#的面向对象结构。

使用这个方法要小心!

下面是使用可选参数声明和编写函数的方法:

unsafe public void OptionalOutParameter(int* pOutParam = null)
{
int lInteger = 5;
// If the parameter is NULL, the caller doesn't care about this value.
if (pOutParam != null)
{
// If it isn't null, the caller has provided the address of an integer.
*pOutParam = lInteger; // Dereference the pointer and assign the return value.
}
}

然后像这样调用函数:

unsafe { OptionalOutParameter(); } // does nothing
int MyInteger = 0;
unsafe { OptionalOutParameter(&MyInteger); } // pass in the address of MyInteger.

为了进行编译,您需要在项目选项中启用不安全代码。这是一个通常不应该使用的解决方案,但如果你为了一些奇怪的,神秘的,管理灵感的决定,确实需要一个可选的out参数在c#中,那么这将允许你这样做。

像这样怎么样?

public bool OptionalOutParamMethod([Optional] ref string pOutParam)
{
return true;
}

你仍然需要从c#中传递一个值给参数,但它是一个可选的ref参数。

void foo(ref int? n)
{
return null;
}

ICYMI:包含在c# 7.0枚举在这里的新特性中,"discards"现在被允许以_的形式作为out参数,让你忽略你不关心的out参数:

p.GetCoordinates(out var x, out _); // I only care about x

附注:如果你也对“输出变量x”的部分感到困惑,请阅读链接上关于“输出变量”的新功能。

不能,但你可以使用委托(例如Action)作为替代。

当我想要一个可选的out参数时,部分受到Robin R的回答的启发,我转而使用Action委托。我借用了他的示例代码来修改Action<int>的使用,以显示差异和相似之处:

public string foo(string value, Action<int> outResult = null)
{
// .. do something


outResult?.Invoke(100);


return value;
}


public void bar ()
{
string str = "bar";


string result;
int optional = 0;


// example: call without the optional out parameter
result = foo (str);
Console.WriteLine ("Output was {0} with no optional value used", result);


// example: call it with optional parameter
result = foo (str, x => optional = x);
Console.WriteLine ("Output was {0} with optional value of {1}", result, optional);


// example: call it with named optional parameter
foo (str, outResult: x => optional = x);
Console.WriteLine ("Output was {0} with optional value of {1}", result, optional);
}

这样做的好处是,可选变量在源代码中作为普通int出现(编译器将其包装在闭包类中,而不是我们显式地将其包装在用户定义的类中)。

变量需要显式初始化,因为编译器不能假定Action将在函数调用退出之前被调用。

它并不适合所有的用例,但是对于我的实际用例(一个为单元测试提供数据的函数,并且一个新的单元测试需要访问一些没有在返回值中出现的内部状态)来说效果很好。

对于c# 6.0及以下版本,使用不带out形参的重载方法调用带out形参的方法。当被特别问到c# 4.0是否可以有一个可选的out参数时,我不确定为什么用于。net Core的c# 7.0甚至是这个线程的正确答案。答案是否定的!

对于简单类型,您可以使用不安全的代码来实现这一点,尽管这不是惯用的,也不是推荐的。像这样:

// unsafe since remainder can point anywhere
// and we can do arbitrary pointer manipulation
public unsafe int Divide( int x, int y, int* remainder = null ) {
if( null != remainder ) *remainder = x % y;
return x / y;
}

也就是说,没有理论上的原因,c#最终不能使用安全的代码,比如下面的代码:

// safe because remainder must point to a valid int or to nothing
// and we cannot do arbitrary pointer manipulation
public int Divide( int x, int y, out? int remainder = null ) {
if( null != remainder ) *remainder = x % y;
return x / y;
}

事情可能会变得有趣:

// remainder is an optional output parameter
// (to a nullable reference type)
public int Divide( int x, int y, out? object? remainder = null ) {
if( null != remainder ) *remainder = 0 != y ? x % y : null;
return x / y;
}

这个直接的问题已经在其他得到好评的答案中得到了回答,但有时根据你想要实现的目标考虑其他方法是值得的。

如果你想要一个可选参数,允许调用者可能从你的方法请求额外的数据,根据这些数据来做决定,另一种设计是将决策逻辑移到你的方法中,并允许调用者可选地传入该决策标准的值。例如,这里有一个确定矢量罗盘点的方法,在这个方法中,我们可能想要回传矢量的大小,这样调用者就可以潜在地决定在指南针点判断距离原点足够远之前,是否应该达到某个最小阈值,从而明确有效:

public enum Quadrant {
North,
East,
South,
West
}


// INVALID CODE WITH MADE-UP USAGE PATTERN OF "OPTIONAL" OUT PARAMETER
public Quadrant GetJoystickQuadrant([optional] out magnitude)
{
Vector2 pos = GetJoystickPositionXY();
float azimuth = Mathf.Atan2(pos.y, pos.x) * 180.0f / Mathf.PI;
Quadrant q;
if (azimuth > -45.0f && azimuth <= 45.0f) q = Quadrant.East;
else if (azimuth > 45.0f && azimuth <= 135.0f) q = Quadrant.North;
else if (azimuth > -135.0f && azimuth <= -45.0f) q = Quadrant.South;
else q = Quadrant.West;
if ([optonal.isPresent(magnitude)]) magnitude = pos.Length();
return q;
}

在这种情况下,我们可以移动“最小幅度”;将逻辑引入到方法中,并以一个更清晰的实现结束,特别是因为计算大小涉及到一个平方根,所以如果我们只想做大小的比较,那么计算效率很低,因为我们可以用平方值来比较:

public enum Quadrant {
None, // Too close to origin to judge.
North,
East,
South,
West
}


public Quadrant GetJoystickQuadrant(float minimumMagnitude = 0.33f)
{
Vector2 pos = GetJoystickPosition();
if (minimumMagnitude > 0.0f && pos.LengthSquared() < minimumMagnitude * minimumMagnitude)
{
return Quadrant.None;
}
float azimuth = Mathf.Atan2(pos.y, pos.x) * 180.0f / Mathf.PI;
if (azimuth > -45.0f && azimuth <= 45.0f) return Quadrant.East;
else if (azimuth > 45.0f && azimuth <= 135.0f) return Quadrant.North;
else if (azimuth > -135.0f && azimuth <= -45.0f) return Quadrant.South;
return Quadrant.West;
}

当然,这可能并不总是可行的。由于其他答案提到了c# 7.0,如果你真正要做的是返回两个值,并允许调用者可选地忽略其中一个,那么惯用的c#将返回两个值的元组,并使用带有位置初始化器的c# 7.0的元组和_ "discard"参数:

public (Quadrant, float) GetJoystickQuadrantAndMagnitude()
{
Vector2 pos = GetJoystickPositionXY();
float azimuth = Mathf.Atan2(pos.y, pos.x) * 180.0f / Mathf.PI;
Quadrant q;
if (azimuth > -45.0f && azimuth <= 45.0f) q = Quadrant.East;
else if (azimuth > 45.0f && azimuth <= 135.0f) q = Quadrant.North;
else if (azimuth > -135.0f && azimuth <= -45.0f) q = Quadrant.South;
else q = Quadrant.West;
return (q, pos.Length());
}


(Quadrant q, _) = GetJoystickQuadrantAndMagnitude();
if (q == Quadrant.South)
{
// Do something.
}