6

次のようなジェネリック メソッドを定義する基本クラスがあります。

public class BaseClass
{
    public T DoSomething<T> ()
    { ... }
}

このクラスはサードパーティ製であり、インターフェイスが付属していないため、そのクラスから実際に必要なメソッドを定義するインターフェイスを定義しています。そうすれば疎結合になり、実際にそのサードパーティ クラスを別のものと交換できます。この例では、次のインターフェイスを検討してください。

public interface ISomething
{
    T DoSomething<T> ()
        where T : Foo;
}

ご覧のとおり、同じメソッドを定義していますが、型パラメータに型制約も適用しています。これは、これに関係のない他の要件から来ています。

次に、BaseClassも実装するサブタイプを定義しますISomething。このクラスは、インターフェースの背後にある通常の実装として使用されますが、インターフェースはアプリケーションの残りの部分がアクセスするものになります。

public class Something : BaseClass, ISomething
{
    // ...
}

はすでに任意の型パラメータをサポートDoSomethingしているため、特に のサブタイプである型パラメータをサポートする必要があります。したがって、 のサブタイプがすでにインターフェースを実装していると予想されます。ただし、次のエラーが表示されます。BaseClassTFooBaseClass

メソッド 'BaseClass.DoSomething()' の型パラメーター 'T' の制約は、インターフェイス メソッド 'ISomething.DoSomething()' の型パラメーター 'T' の制約と一致する必要があります。代わりに、明示的なインターフェイスの実装を使用することを検討してください。

さて、私には 2 つの可能性があります。最初のものは、エラーが示唆することを行い、インターフェースを明示的に実装することです。2 つ目は、次を使用して基本実装を非表示にすることnewです。

// Explicit implementation
T ISomething.DoSomething<T> ()
{
    return base.DoSomething<T>();
}

// Method hiding
public new T DoSomething<T>()
    where T : Foo
{
    return base.DoSomething<T>();
}

どちらも機能しますが、クラス自体からメソッドにアクセスできるようにするために、おそらく2番目のソリューションを好むでしょう。ただし、次の疑問が残ります。

基本型がより厳密でない (読み取り: なし) 型制約を使用してメソッドを既に実装している場合、なぜメソッドを再実装する必要があるのですか? メソッドをそのまま実装する必要があるのはなぜですか?

編集:メソッドにもう少し意味を持たせるために、戻り値の型を から に変更しvoidましたT。私の実際のアプリケーションでは、ジェネリック引数と戻り値の両方があります。

4

3 に答える 3

3

継承の代わりに構成を使用して実装してみてくださいSomething:

public class Something : ISomething
{
    private readonly BaseClass inner = ...;

    void DoSomething<T>() where T : Foo
    {
        inner.DoSomething<T>();
    }
}
于 2013-01-30T15:29:03.220 に答える
1

以下のコードであなたが望むものを手に入れることができます。インターフェイス定義にtypeパラメータを含めることで、コンパイラを満足させるように見える共変にすることができます。Baseクラスは変更されないままであり、実装をシャドウイングして、単一のメソッドでインターフェースを実装することができますBase

class Program
{
    static void Main()
    {
        var something = new Something<Foo>();
        var baseClass = (BaseClass)something;
        var isomething = (ISomething<Foo>)something;

        var baseResult = baseClass.DoSomething<Bar>();
        var interfaceResult = isomething.DoSomething<Bar>();
        var result = something.DoSomething<Bar>();
    }
}

class Foo 
{
}

class Bar : Foo
{
}

class BaseClass
{
    public T DoSomething<T>()
    {
        return default(T);
    }
}

interface ISomething<out T> where T : Foo
{
    T DoSomething<T>();
}

class Something<T> : BaseClass, ISomething<T> where T : Foo
{
    public new T DoSomething<T>()
    {
        return default(T);
    }
}

Fooまたは、インスタンス化で本当に指定したくない場合

class Program
{
    static void Main()
    {
        var something = new Something();
        var baseClass = (BaseClass)something;
        var isomething = (ISomething)something;

        var baseResult = baseClass.DoSomething<Bar>();
        var interfaceResult = isomething.DoSomething<Bar>();
        var result = something.DoSomething<Bar>();
    }
}

class Foo 
{
}

class Bar : Foo
{
}

class BaseClass
{
    public T DoSomething<T>()
    {
        return default(T);
    }
}

interface ISomething
{
    T DoSomething<T>;
}

interface ISomething<S> : ISomething where S : Foo
{
    new R DoSomething<R>() where R : Foo;
}

class Something : BaseClass, ISomething
{
    public new T DoSomething<T>()
    {
        return default(T);
    }
}
于 2013-01-30T15:43:35.457 に答える
1

確かに、指定されたコードは安全にコンパイルおよび実行できます。

Somethingインスタンスが asSomethingまたは asと型指定されている場合BaseClass、コンパイラは の任意の型を許可しTますが、同じインスタンスが asISomethingと型指定されている場合は、型の継承のみを許可しますFoo。どちらの場合も、静的チェックと実行時の安全性は通常どおり得られます。

実際、上記のシナリオは、明示的に実装した場合とまったく同じです。ISomethingでは、現在の状況に賛成し、反対することができる議論を見てみましょう。

為に:

  • 提案された解決策は、すべてのケースに適用できるわけではありません。それは正確なメソッドシグネチャに依存します (型引数は共変ですか? 反変ですか? 不変ですか?)
  • そのようなケースがどのように処理されるかを示す新しいテキストで仕様を修正する必要はありません。
  • これにより、コードが自己文書化されます。そのテキストを学習する必要はありません。明示的なインターフェースの実装に関する現在のルールで十分です
  • C# コンパイラ チームに開発コストを課すことはありません (ドキュメント、機能の実装、テストなど)。

に対して:

  • もっと入力する必要があります

上記のことと、これが日常的なシナリオではないという事実を考慮すると、到達すべき結論は明らかです。

于 2013-01-30T15:41:55.760 に答える