2017-08-08 14 views
1

私はタイプを介してコンパイル時の依存性検証を達成しようとしています。結局のところ、これは依存性注入の使用に由来しています。私はServiceCollectionに依存性を追加するのを忘れてしまいます。私は一種のビルダーを持っていて、依存するものが既に追加されていない限り、アイテムを追加できないようにしたいとします。私はこれが一例で最もよく説明されると思う。また、成功するビルダーパターン、リターンタイプを増やす?

var myBuilder = MyBuilder(configuration) 
    .AddThing() 
    .AddMyServiceRequiresThing() // dependency satisfied by AddThing() 
    .AddThing2() 
    .AddMyService2RequiresThing2(); // dependency satisfied by AddThing2() 

そしてもちろん以下:基本的に

var myBuilder = MyBuilder(configuration) 
    .AddThing() 
    .AddThing2() 
    .AddMyServiceRequiresThing() // dependency satisfied by AddThing() 
    .AddMyService2RequiresThing2(); // dependency satisfied by AddThing2() 

を、ときに私は次のように失敗したいと思います:

var myBuilder = new MyBuilder(configuration) 
    .AddMyServiceRequiresThing()  // dependency not satisfied, type error 
    .AddMySecondServiceRequiresThing2() // dependency not satisfied, type error 
    .AddMyThing() 
    .AddMyThing2(); 

をしかし、私は成功するために、次のようになります「Thing」を追加することで、ビルダーのタイプを強化して、現在そのことを提供してほしいと思っています。しかし、 "MyService"と "MyService2"は異なるものを必要とする可能性があるため、元のBuilderタイプ(拡張機能なし)を返すことができず、必要なものを返すことはできません。

私はいくつかのジェネリック魔法を試しましたが、どこにも巻き込まれていません。私はこれを実行時ロジックを介して強制することができますが、私は型システムが私のためにそれをやりたいと思っています。これを行うには、C#の型システムは十分強力ですか?すべての情報はコンパイル時に入手できますが、表現できるかどうかわかりません。

+0

"これを行うのに十分な強さのC#のシステムですか?"そして静的に型付けされた他の言語がありますか? –

+1

コンパイル時に次のようにチェックすることができます。 Bool変数をfalseに初期化しました。 Addthing()でそのbool変数をtrueに設定し、requiresthing()でそれをチェックすると、セクションに決して到達しないことをVisual Studioが警告した場合、コンパイル時にチェックすることができます。 さらに、例外がプログラミングエラーを処理することができます。 – gismo

+0

@WiktorZychla TBH私は、この種のパターンが可能かどうかを知るために、Haskellのようなより強力な型システムに慣れていません。 – jrk

答えて

1

このような機能を探すのが正しいとは確信していません。提供する例では論理的にすることができますが、MyBuilderオブジェクトは、追跡されないコードの部分、またはコンパイル時に追跡することができない別のスレッドで構築および/または変更することができます時間。 サンプルでも、MyBuilderのコンストラクターで何が起こっているのか明確ではありません。また、そのデータとビルダーを更新してしまうと、あなたは

class Data1 
{ 
    public void Update(MyBuilder builder) 
    { 
     builder.AddThing(); // or anything else 
    } 
} 

ビルド関数を呼び出すだろうと誰もが正確に機能

にオブジェクトに起こったことができるかを確認することができますクラスのメソッドを持つことができます 「物事」の量やsequenseはない事柄を実行した場合

多分

class Builder2 
{ 
    public Builder2(MyBuilder builder) 
    { 
     _thing1 = builder.Thing1; 
     AddThing2(); 
    } 
    public XXX AddMyService2RequiresThing2() 
    { 
     ... 
    } 
} 

などの複合ビルダーを含む、異なるビルダーを持っている方が良いだろう、それはSOUNだ式ツリーは、C#で実装されているかを見てみましょうあなたが望むものに近いds。異なる表現/委任を構築することができ、コンパイル段階を適切に制御できます。ビルダー プロデュース - - ビルダーに保存され、それ に追加のデータを提供 - 単一の「シング」/ブロックを持って :もし、あなたがその別のオブジェクトにコードを広めることができ

を一般化したくないものを一般化しないでくださいでも実装がする場合は、エンド・ビルダーでresponcibility

public class Builder 
{ 
    // Builder will have access to all fields of implementation 
    // which will be hidden from other part of code 
    private ImplThinger1 ImplThinger1; 
    // other Thinger implementations 

    public IThinger1 AddThing1() 
    { 
     return ImplThinger1 ?? (ImplThinger1 = new ImplThinger1()); 
    } 
} 

public interface IThinger1 
{ 
    void AddServiceReqThing1(); 
} 

public class ImplThinger1 : IThinger1 
{ 
    // accessible in builder and not outside 
    // if it's not created outside 
    public Thing Data { get; } 

    public void AddServiceReqThing1() 
    { 
     // Stuff 
    } 
} 

が追加されたすべての実装を持つことになりますし、それだけでBuilderを使用して構築されますIThingerインターフェイスの実装を通じて直接ビルダーにサービスを追加することはできませんでしょうIThingerを作成すると作成されますwoun't be in Builderに入れました

+0

初期ビルダーチェーン外のビルダーに物を追加することは、その物が追加されたかどうかを決定するビルダーの責任です。それは大丈夫です。 – jrk

+0

SOは長いコメントの編集を許可していません。戻り、コメントが保存されます。 TIL。 私が望むのは、プロバイダがビルダーの戻り値の型を変更して、ビルダーが何をしていてもそのことを提供していると言うことです。 DIを介してIMyCacheServiceの使用をサポートすることを宣言するようにBuilderを変更する "AddMyCacheService"メソッドを考えてみましょう。 IMyCacheServiceのコンシューマは、そのAddMyCacheServiceメソッドが呼び出されるまで、ビルダーに自身を追加できません。 – jrk

+0

それぞれのビルド後に実行され、誤った設定で失敗するというユニットテストを実行する方が良いと言えます。 – ASpirin