🗂
C#: out引数がdiscard(破棄)されているかどうか判定する実験
はじめにお断りとして、本記事は実用性を狙ったものではありません。
ネタ
C#7.0以降、_
によるdiscard(破棄)が使えるようになりました。その用例の1つがout引数です。
bool isInt = int.TryParse("12345", out _);
本記事は、このように out _
として捨てられてしまうのか、または out var x
のようにちゃんと受け取ってくれたのかを、呼び出されたメソッド内で判定できるのか?というのをやってみるものです。
先に結論
以下はC# 10.0 (.NET 6) 前提です。
void Foo(out int v, [CallerArgumentExpression("v")] string expression = "")
{
v = default;
if (expression != "_")
{
System.Threading.Thread.Sleep(1000); // 重い計算
v = 12345;
}
}
この例のように、discardされないときのみoutのため頑張って計算する、のようなシーンがたまにあるかもという仮定で考えてみました。
後述しますが限界があります。
繰り返しますが実用すべきものではないと考えます。これに近いことをやりたければdiscard版を別メソッドかオーバーロードで用意するのが良いでしょう。
CallerArgumentExpression について
C# 10.0から使えるようになった CallerArgumentExpression
という属性があります。
どのような式を渡したのかを文字列として得ることができます。
using System;
using System.Runtime.CompilerServices;
Test("Hello World");
Test("Hello" + "World");
Test(() => "Hello World");
Test(new[]{"Hello World"}[0]);
void Test(object obj, [CallerArgumentExpression("obj")] string expression = "")
{
Console.WriteLine(expression);
}
"Hello World"
"Hello" + "World"
() => "Hello World"
new[]{"Hello World"}[0]
discard + CallerArgumentExpression
CallerArgumentExpressionを使えば、discardすなわち _
があるかどうか文字列としてわかります。本実験的にはありがたいことに、out
キーワードは文字列に含まれません。
int x;
Foo(out x);
Foo(out int y);
Foo(out _);
void Foo(out int v, [CallerArgumentExpression("v")] string expression = "")
{
v = default;
Console.WriteLine(expression);
}
x
int y
_
ということで、再掲になりますが一応はこれで希望のことはできそうすね。
void Foo(out int v, [CallerArgumentExpression("v")] string expression = "")
{
v = default;
if (expression != "_")
{
System.Threading.Thread.Sleep(1000); // 重い計算
v = 12345;
}
}
引数が複数ある場合
CallerArgumentExpressionは複数個の引数があっても大丈夫です。
Foo2(out _, out _);
Foo2(out int x, out int y);
void Foo2(
out int a, out int b,
[CallerArgumentExpression("a")] string expressionA = "",
[CallerArgumentExpression("b")] string expressionB = "")
{
a = default;
b = default;
Console.WriteLine("a: {0}", expressionA);
Console.WriteLine("b: {1}", expressionB);
}
a: _
b: _
a: int x
b: int y
条件分岐がめんどくさそうですが、従って2個以上outがあっても同様にできますね。
制限事項
_
が使われたからと言ってdiscardとは限りません。
int _;
Foo(out _);
Console.WriteLine(_); // 捨ててないから12345を期待するが、0
void Foo(out int v, [CallerArgumentExpression("v")] string expression = "")
{
v = default;
if (expression != "_")
{
v = 12345;
}
}
以上で実験は終わりです。簡単にできる範囲では、ほかに良い手は思い浮かびませんでした。思考のメモとして残します。
Discussion