C# では、メソッドを定義できるようにする必要がありますが、1 つまたは 2 つの戻り値の型を返す必要があります。実行しようとすると、コンパイラはエラーを返しますが、どのメソッドを呼び出す必要があるかを知るほど賢くないのはなぜですか?
int x = FunctionReturnsIntOrString();
戻り値の型が異なる 2 つの関数をコンパイラが禁止するのはなぜですか?
C# では、メソッドを定義できるようにする必要がありますが、1 つまたは 2 つの戻り値の型を返す必要があります。実行しようとすると、コンパイラはエラーを返しますが、どのメソッドを呼び出す必要があるかを知るほど賢くないのはなぜですか?
int x = FunctionReturnsIntOrString();
戻り値の型が異なる 2 つの関数をコンパイラが禁止するのはなぜですか?
C#3.0言語仕様のセクション1.6.6の最後の段落から:
メソッドのシグニチャは、メソッドが宣言されているクラスで一意である必要があります。メソッドのシグニチャは、メソッドの名前、タイプパラメータの数、およびそのパラメータの数、修飾子、およびタイプで構成されます。メソッドのシグニチャには、戻り型は含まれていません。
ILでは、2つのメソッドはリターンタイプのみで異なる可能性がありますが、リフレクション以外では、リターンタイプのみが異なるメソッドを呼び出す方法はありません。
この特定のシナリオでは明らかかもしれませんが、実際には明らかでない多くのシナリオがあります。例として次の API を見てみましょう
class Bar {
public static int Foo();
public static string Foo();
}
次のシナリオでは、コンパイラがどの Foo を呼び出すかを知ることは不可能です
void Ambiguous() {
Bar.Foo();
object o = Bar.Foo();
Dog d = (Dog)(Bar.Foo());
var x = Bar.Foo();
Console.WriteLine(Bar.Foo());
}
これらは簡単なサンプルのほんの一部です。もっと不自然で邪悪な問題が確かに存在します。
ジェネリックを使用して正しい型を返すことについて考えたことはありますか。
public T SomeFunction<T>()
{
return T
}
int x = SomeFunction<int>();
string y = SomeFunction<string>();
注: このコードはテストされていません
戻り値のみが異なる関数は、オーバーロードの対象外です。
int x = FunctionReturnsIntOrString();
double y = FunctionReturnsIntOrString();
上記の場合、コンパイラは正しい関数を識別できますが、戻り値が指定されていない場合を考慮すると、あいまいです。
FunctionReturnsIntOrString(); //int version
FunctionReturnsIntOrString(); //double version
コンパイラは、ここでオーバーロードされたメソッドを解決できません。
質問で示した構文を取得することは可能ですが、そこにたどり着くにはかなりの賢さが必要です。
もう少し具体的に問題を設定してみましょう。ここで重要な点は構文だと思います。これが欲しい:
int intValue = GetData();
string stringValue = GetData();
DateTime[] dateTimeArrayValue = GetData();
ただし、これを機能させたくありません。
double doubleValue = GetData();
DateTime? nullableDateTimeValue = GetData();
これを実現するには、 からの戻り値で中間オブジェクトを使用する必要がGetData()
あります。その定義は次のようになります。
public class Data
{
public int IntData { get; set; }
public string StringData { get; set; }
public DateTime[] DataTimeArrayData { get; set; }
public MultiReturnValueHelper<int, string, DateTime[]> GetData()
{
return new MultiReturnValueHelper<int, string, DateTime[]>(
this.IntData,
this.StringData,
this.DataTimeArrayData);
}
}
もちろん、実装はまったく異なりますが、これで十分です。を定義しましょうMultiReturnValueHelper
。
public class MultiReturnValueHelper<T1, T2, T3> : Tuple<T1, T2, T3>
{
internal MultiReturnValueHelper(T1 item1, T2 item2, T3 item3)
: base(item1, item2, item3)
{
}
public static implicit operator T1(MultiReturnValueHelper<T1, T2, T3> source)
{
return source.Item1;
}
public static implicit operator T2(MultiReturnValueHelper<T1, T2, T3> source)
{
return source.Item2;
}
public static implicit operator T3(MultiReturnValueHelper<T1, T2, T3> source)
{
return source.Item3;
}
}
一般的なケースではT1
、T2
、 、 などについてこの定義を繰り返します。T3
また、戻り値ヘルパーをそれを返すクラスまたはメソッドに非常に密接にバインドすることもできます。これにより、個別の型のセットを取得して割り当てることができるインデクサーに対して、これと同じ種類の効果を作成できます。ここが一番使いやすかったです。
別のアプリケーションでは、次のような構文を有効にします。
data["Hello"] = "World";
data["infamy"] = new DateTime(1941, 12, 7);
data[42] = "Life, the universe, and everything";
この構文を実現するための正確なメカニズムは、読者の課題として残されています。
この問題に対するより一般的な解決策 (差別された組合の問題と同じだと思います) については、その質問に対する私の回答を参照してください。
自分が何をどのように行っているかを真剣に再考したいと思いますが、これは可能です。
int i = FunctionReturnsIntOrString<int>();
string j = FunctionReturnsIntOrString<string>();
このように実装することによって:
private T FunctionReturnsIntOrString<T>()
{
int val = 1;
if (typeof(T) == typeof(string) || typeof(T) == typeof(int))
{
return (T)(object)val;
}
else
{
throw new ArgumentException("or some other exception type");
}
}
しかし、そうしない理由はたくさんあります。
戻り値の型はメソッドのシグネチャの一部ではなく、名前とパラメーターの型のみです。このため、戻り値の型だけが異なる 2 つのメソッドを持つことはできません。これを回避する 1 つの方法は、メソッドがオブジェクトを返すことです。呼び出し元のコードは、それを int または string にキャストする必要があります。
ただし、2 つの異なるメソッドを作成するか、int または string のいずれかを含むことができるメソッドから返されるクラスを作成する方がよい場合があります。
場合によっては、どちらを使用する必要があるかを本当に判断できないためです(あなたの例では使用できますが、すべてのケースが明確であるとは限りません):
void SomeMethod(int x) { ... }
void SomeMethod(string x) { ... }
このコンテキストでSomeMethod(FunctionReturnsIntOrString())
、コンパイラは何を呼び出す必要がありますか?
また、呼び出されたメソッドに値を割り当てる必要はありません。例えば:
int FunctionReturnsIntOrString() {return 0;}
string FunctionReturnsIntOrString() {return "some string";}
//some code
//...
int integer = FunctionReturnsIntOrString(); //It probably could have figured this one out
FunctionReturnsIntOrString(); //this is valid, which method would it call?
C# には戻り値の型のオーバーライドはありません。IL はその種のオーバーライドをサポートしていますが、C# はまだサポートしていません。