私はちょうど同じ問題に直面しました。オプション 2 (拡張メソッドを使用) は、メソッドの下限を仮想にする (したがって、オブジェクトの動的な型に基づいてディスパッチする) 必要があるまでは、非常にうまく機能します。それが必要な場合は、オプション 3 (インターフェイスの分散とよく知られているビジター パターン) を使用した実行可能なソリューションを次に示します。
同等を達成するために
public class A<T> // an argument to the generic method
{
}
public class B<S>
{
public virtual R Fun<T>(A<T> arg) where S : T // illegal in C#/CLR
{
...
}
}
public class C<S> : B<S>
{
public override R Fun<T>(A<T> arg)
{
}
}
次のことを行います。まず、操作を実行するためのインターフェースを定義します (ここではビジター パターンを使用するため、オーバーライドする型ごとに個別のメソッドが必要ですFun
)。
public interface IFun<in T>
{
R Fun<S>(B<S> self) where S : T;
R Fun<S>(C<S> self) where S : T;
}
ジェネリック パラメータT
は制約としてのみ使用されるため、インターフェイスはそれに関して反変である可能性があることに注意してください。これを使用して、操作B
によってC
「訪問」されます。
public class B<S>
{
public virtual R Perform(IFun<S> fun)
// contravariant, any IFun<T> with S : T will be accepted
{
return fun.Fun(this);
}
}
public class C<S> : B<S>
{
public override R Perform(IFun<S> fun)
{
return fun.Fun(this);
}
}
実際に引数を使用して操作を実行するにはA<T>
、インターフェイスを実装する構造体/クラスでラップします。
public struct TheFun<T> : IFun<T>
{
public A<T> arg;
R IFun<T>.Fun<S>(B<S> self)
{
... body of B<S>.Fun(A<T> arg) ...
}
R IFun<T>.Fun<S>(C<S> self)
{
... body of C<S>.Fun(A<T> arg) ...
}
}
最後に、オプション 2 のように拡張メソッドを導入します。
public static class Extensions
{
public static R Fun<S,T>(this B<S> self, A<T> arg) where S : T
{
return self.Perform(new TheFun<T> { arg = arg });
}
}
終わり。単一のキャストや型チェックなしで機能します。主な欠点は次のとおりです。
- それは非常に複雑で (ただし、コード サイズは一定の要因だけ意図したものよりも長くなります)、コードを読んでいる人に嫌われる可能性があります。
- 実装は から
B
およびC
に移動されたため、およびTheFun
の必要なメンバーはそこでアクセスできるようにする必要があります。B
C