これは、C# コンパイラがオーバーロード解決を実行する方法と、バインドするメソッドを決定する方法のため、サポートが難しいユース ケースです。
最初の問題は、制約がメソッドのシグネチャの一部ではなく、オーバーロードの解決とは見なされないことです。
克服しなければならない 2 番目の問題は、コンパイラが利用可能なシグネチャから最適な一致を選択することです。これは、ジェネリックを扱う場合、一般に、特に次のようなパラメータがある場合にSomeMethod<T>(T)
、より適切な一致と見なされることを意味します。または。SomeMethod<T>( IEnumerable<T> )
T[]
List<T>
しかし、より基本的には、単一の値に対する操作と値のコレクションに対する操作が本当に同じ操作かどうかを考慮する必要があります。論理的に異なる場合は、わかりやすくするために別の名前を使用することをお勧めします。おそらく、単一のオブジェクトとオブジェクトのコレクションとの間のセマンティックの違いは意味がないと主張できるいくつかのユースケースがあります...しかし、その場合、なぜ2つの異なるメソッドを実装する必要があるのでしょうか? メソッドのオーバーロードが違いを表現する最良の方法であるかどうかは不明です。混乱を招きやすい例を見てみましょう。
Cache.GetOrAdd("abc", () => context.Customers.Frobble() );
まず、上記の例では、戻りパラメーターを無視することを選択していることに注意してください。Frobble()
次に、Customers
コレクションに対して何らかのメソッドを呼び出していることに注意してください。GetOrAdd()
では、どのオーバーロードが呼び出されるか教えていただけますか? 明らかに、Frobble()
返す型を知らなければ、それは不可能です。個人的には、セマンティクスが構文から容易に推測できないコードは、可能な限り避けるべきだと考えています。より適切な名前を選択すると、この問題は軽減されます。
Cache.Add( "abc", () => context.Customers.Frobble() );
Cache.AddRange( "xyz", () => context.Customers.Frobble() );
最終的に、例のメソッドを明確にするためのオプションは 3 つしかありません。
- いずれかのメソッドの名前を変更します。
IEnumerable<T>
2 番目のオーバーロードを呼び出す場所にキャストします。
- コンパイラが区別できるように、メソッドの 1 つのシグネチャを変更します。
オプション 1 は自明であるため、これ以上は説明しません。
オプション 2 も理解しやすいです。
var customers = Cache.GetOrAdd("All",
() => (IEnumerable<Customer>)context.Customers.ToArray());
オプション 3 はより複雑です。それを達成する方法を見てみましょう。
アプローチでは、デリゲートの署名を変更Func<>
します。たとえば、次のようになります。
T GetOrAdd<T> (string cachekey, Func<object,T> fnGetItem)
T[] GetOrAdd<T> (string cachekey, Func<IEnumerable<T>> fnGetItem)
// now we can do:
var customer = Cache.GetOrAdd("First", _ => context.Customers.First());
var customers = Cache.GetOrAdd("All", () => context.Customers.ToArray());
個人的には、このオプションはひどく醜く、直感的ではなく、混乱を招くと思います。未使用のパラメーターを導入するのはひどいです...しかし、残念ながらうまくいきます。
署名を変更するもう 1 つの方法 (それほどひどい方法ではありません) は、戻り値をout
パラメーターにすることです。
void GetOrAdd<T> (string cachekey, Func<object,T> fnGetItem, out T);
void GetOrAdd<T> (string cachekey, Func<IEnumerable<T>> fnGetItem, out T[])
// now we can write:
Customer customer;
Cache.GetOrAdd("First", _ => context.Customers.First(), out customer);
Customer[] customers;
var customers = Cache.GetOrAdd("All",
() => context.Customers.ToArray(), out customers);
しかし、これは本当に良いですか?これらのメソッドを他のメソッド呼び出しのパラメーターとして使用できなくなります。また、コードが不明確になり、理解しにくくなります、IMO。
提示する最後の代替手段は、戻り値の型を識別する別の汎用パラメーターをメソッドに追加することです。
T GetOrAdd<T> (string cachekey, Func<T> fnGetItem);
R[] GetOrAdd<T,R> (string cachekey, Func<IEnumerable<T>> fnGetItem);
// now we can do:
var customer = Cache.GetOrAdd("First", _ => context.Customers.First());
var customers = Cache.GetOrAdd<Customer,Customer>("All", () => context.Customers.ToArray());
したがって、ヒントを使用して、コンパイラがオーバーロードを選択するのを助けることができます...確かに。しかし、そこにたどり着くために開発者としてしなければならない余分な作業をすべて見てください (導入された醜さと間違いの可能性は言うまでもありません)。それは本当に努力する価値がありますか?特に、私たちを助けるための簡単で信頼できる手法 (メソッドに別の名前を付ける) が既に存在する場合はどうでしょうか?