強制的な汎用パラメータの型は、特定のクラスではなくインタフェースです。

これらのインタフェースと実装を考えると

interface IService {}
class Service : IService {}

一般的な方法で

void Register(I service)
{
    var type = typeof(I);
}

ジェネリック型について次の行を一貫させるにはどうすればよいですか?

Register(new Service())   //type is 'IService'
Register(new Service());            //type is 'Service'

2つのオプションを使用できます。

  • 両方の行は、 IServie 型を使用してコンパイルされます。
  • 2行目のコンパイルに失敗します
2
重複している可能性があります

3 答え

あなたは型パラメータをインターフェイスにすることを禁じる方法がありません。

もちろん、実行時間チェックを実行して、 I がインタフェースでない場合は例外をスローすることができます。 (別名として、命名規則は I ではなく T でなければならないことを示唆しています)。

7
追加された
@sil:わかりません - コード契約を使ってこのようなことについて質問する別の質問を覚えているようですが、うまくいきませんが、間違っている可能性があります。
追加された 著者 Jon Skeet,
おそらく何とかコード契約を使用していますか?
追加された 著者 sll,

I のタイプを IService に制限するのは正しいでしょうか?

where 句を使用してジェネリックタイプを制限する

void Register(TServiceType service)
    where TServiceType : IService

あなたが求めているのは、パラメータを任意のインタフェースに制限することです.Jonが述べたように(ただし、私の答えはあまり冗長ではありません)、これは違法です。

2
追加された
私はあなたがその質問を誤解したと思います。私は、 I を具体的なクラスではなくインターフェイス(すべてのインターフェイス)に制約することがアイディアだと考えています。
追加された 著者 Jon Skeet,
私はそれがポイントだと思った...
追加された 著者 Grant Thomas,
@JonSkeet私は誤解が理にかなっています。私は、そうでなければ、私たちが他のものを見つけるまで、これを当分の間守っていきます。しかし、あなたはおそらく正しいでしょう。もしそうなら、私はあなたがそれでやりたいことではありません。
追加された 著者 Grant Thomas,
これにより、インターフェースを実装したすべてのタイプ
追加された 著者 Ben Robinson,

As you note, Register(new Service()); of course compiles to Register(new Service());

具体的な型の実装されたインタフェースの1つを登録するインタフェース(大きな前提です)として選択するロジックを定義できると仮定すると、コンパイラに除外させるのではなく、具体的な型を扱うことができます。明らかに、些細な解決策は、型が1つのインタフェースしか実装しないようにすることですが、それは非常に有用ではありそうにありません。

私はこれらの行に沿って何かを考えています(Jon Skeetの提案を取り、型パラメータの名前を変更してください):

void Register(T service) 
{ 
    var type = typeof(T); 
    if (type.IsInterface)
    {
        Register(type);
        return;
    }

    var interfaceType = ChooseTheAppropriateInterface(type);
    Register(interfaceType);
} 

void Register(Type typeToRegister)
{
    //...
}

Type ChooseTheAppropriateInterface(Type concreteType)
{
    var interfaces = concreteType.GetInterfaces();
    //... some logic to pick and return the interface to register
}

All things considered, it's probably simplest and clearest to let the caller specify the desired interface with the Register(new Service()); call.

編集

I agree that Register(new Service()); is the clearest form. But how can I enforce programmers to not omit the ? Re-sharper, for instance, may suggest that is redundant.

その質問に答えるために、コールのセマンティクスを考えてみましょう。この呼び出しは、オブジェクト( new Service())をインタフェース( IService )に関連付けます。プログラマーにインターフェースのIDを明示的に指定させる1つの方法は、タイプを仮パラメーターにすることです。実際に、登録しているオブジェクトに対してインターフェースのメソッドを呼び出さない場合は、ジェネリックは必要ありません。

void Register(Type serviceType, object service)
{
   //... some argument validation

    if (!(serviceType.IsAssignableFrom(service.GetType())))
        throw...

   //... register logic
}

//usage:
void InitializeServices()
{
    Register(typeof(IService), new Service());
}

しかし、オブジェクトのインタフェースのメンバを呼び出さなくても、ジェネリックのもう一つの利点はコンパイル時の型チェックです。開発者が型を明示的に指定するのを強制しながらコンパイル時の型チェックを行う方法はありますか? Yes:メソッドのシグネチャに2つの型パラメータがあり、引数は1つのみであるため、コンパイラが両方の型を推論する方法がないため、開発者は両方を提供する必要があります。それはよりタイピングですが、コードはより明示的です。

With that approach, you can also constrain the implementing type to the interface type, to ensure that the developer doesn't call, for example, Register(new SecurityService());:

interface IServiceBase { }//interface that all service interfaces must implement; might not be needed
interface IService : IServiceBase { }
class Service : IService : { }

void Register(TImplementingObject service)
    where TServiceType : IServiceBase //superfluous if there's no IServiceBase, of course
    where TImplementingObject : TServiceType
{
   //... implementation
}

//usage:
void InitializeServices()
{
    Register(new Service());
}

あるいは

class NewImprovedService : Service : { }

void InitializeServices()
{
    Register(new NewImprovedService());
}

これは、おそらく冗長な型の指示のポイントに戻ってきます。呼び出しはあなたが始めたものよりも冗長ですが、開発者が間違ったサービスタイプを誤って登録するのを防ぎます。

しかし、開発者の呼び出しを止めるものは何もないので、元の問題は残っています

Register(new NewImprovedService());

これは typeof(TServiceType).IsInterface のランタイムチェックまで失敗しません。

1
追加された
@ ReuvenBassそれは良い点です。いくつかの考えを編集した答えを見てください。
追加された 著者 phoog,
私は Register (new Service()); が最も明白なフォームであることに同意します。しかし、 を省略しないようにプログラマーにどのように強制することができますか?例えば、よりシャープなのは、 が冗長であることを示唆しているかもしれません。
追加された 著者 Reuven Bass,