API設計で「パラメーターが多すぎる」問題を回避するにはどうすればよいですか?


160

私はこのAPI関数を持っています:

public ResultEnum DoSomeAction(string a, string b, DateTime c, OtherEnum d, 
     string e, string f, out Guid code)

嫌いです。パラメータの順序が不必要に重要になるからです。新しいフィールドを追加するのが難しくなります。何が渡されているかを確認するのは困難です。メソッドを小さな部分にリファクタリングすることは、サブ関数ですべてのパラメータを渡すという別のオーバーヘッドが発生するため、難しくなります。コードは読みにくいです。

私は最も明白なアイデアを思いつきました:データをカプセル化するオブジェクトを持ち、各パラメーターを1つずつ渡すのではなく、渡します。これが私が思いついたものです:

public class DoSomeActionParameters
{
    public string A;
    public string B;
    public DateTime C;
    public OtherEnum D;
    public string E;
    public string F;        
}

これにより、API宣言が次のように減少しました。

public ResultEnum DoSomeAction(DoSomeActionParameters parameters, out Guid code)

いいね。非常に無邪気に見えますが、実際には大きな変更が導入されました。可変性を導入しました。以前行っていたのは、実際には匿名の不変オブジェクト(スタック上の関数パラメーター)を渡すことでした。次に、非常に変更可能な新しいクラスを作成しました。呼び出し元の状態を操作する機能を作成しました。それは最悪です。オブジェクトを不変にしたいのですが、どうすればよいですか?

public class DoSomeActionParameters
{
    public string A { get; private set; }
    public string B { get; private set; }
    public DateTime C { get; private set; }
    public OtherEnum D { get; private set; }
    public string E { get; private set; }
    public string F { get; private set; }        

    public DoSomeActionParameters(string a, string b, DateTime c, OtherEnum d, 
     string e, string f)
    {
        this.A = a;
        this.B = b;
        // ... tears erased the text here
    }
}

ご覧のとおり、実際に元の問題を再現しました。パラメーターが多すぎます。それが進むべき道ではないことは明らかです。私は何をしますか?このような不変性を実現する最後のオプションは、次のような「読み取り専用」構造体を使用することです。

public struct DoSomeActionParameters
{
    public readonly string A;
    public readonly string B;
    public readonly DateTime C;
    public readonly OtherEnum D;
    public readonly string E;
    public readonly string F;        
}

これにより、パラメーターが多すぎるコンストラクターを回避し、不変性を実現できます。実際には、すべての問題(パラメーターの順序など)が修正されます。まだ:

そのとき、私は混乱してこの質問を書くことにしました。可変性を導入せずに「パラメーターが多すぎる」問題を回避するためのC#で最も簡単な方法は何ですか?その目的で読み取り専用の構造体を使用することは可能ですか?それでも悪いAPIデザインはありませんか?

明確化:

  • 単一の責任の原則に違反していないと想定してください。私の元のケースでは、関数は与えられたパラメーターを単一のDBレコードに書き込むだけです。
  • 特定の機能に対する特定の解決策を探しているのではありません。私はそのような問題への一般的なアプローチを求めています。私は特に、可変性やひどい設計を導入せずに「パラメーターが多すぎる」問題を解決することに興味があります。

更新

ここで提供される回答には、さまざまな利点/欠点があります。したがって、これをコミュニティーWikiに変換したいと思います。コードサンプルと長所/短所を含む各回答は、今後同様の問題の良いガイドになると思います。私は今それを行う方法を見つけようとしています。


クリーンコード:ロバートC.マーティンによるアジャイルソフトウェアの職人技のハンドブックとマーティンファウラーのリファクタリングの本はこれを少しカバーしています
Ian Ringrose

1
オプションのパラメーターがあるC#4を使用する場合、そのビルダーソリューションは冗長ではありませんか?
シェラン

1
私は愚かかもしれませんが、これが問題であるかどうかはわかりません。これDoSomeActionParametersは、使い捨てオブジェクトであり、メソッド呼び出しの後に破棄されることになるためです。
Vilx-

6
構造体の読み取り専用フィールドを避けるべきだと言っているわけではないことに注意してください。不変構造体はベストプラクティスであり、読み取り専用フィールドは自己文書化不変構造体の作成に役立ちます。私のポイントは、読み取り専用フィールドが構造体で提供されることを保証するものではないため、変更されないことが観察される読み取り専用フィールドに依存するべきではないということです。これは、値型を参照型であるかのように扱うべきではないという、より一般的なアドバイスの特定のケースです。彼らは非常に異なる動物です。
Eric Lippert、

7
@ssg:私も大好きです。不変性を促進する機能(LINQなど)をC#に追加すると同時に、可変性を促進する機能(オブジェクト初期化子など)を追加しました。不変の型を促進するためのより良い構文があると便利です。私たちはそれについて一生懸命考えており、いくつか面白いアイデアを持っていますが、次のバージョンではそのようなことは期待できません。
Eric Lippert、2011年

回答:


22

フレームワークに採用されているスタイルの1つは、通常、関連するパラメーターを関連するクラスにグループ化するようなものです(ただし、やはり可変性に問題があります)。

var request = new HttpWebRequest(a, b);
var service = new RestService(request, c, d, e);
var client = new RestClient(service, f, g);
var resource = client.RequestRestResource(); // O params after 3 objects

すべての文字列を文字列配列に圧縮することは、それらがすべて関連している場合にのみ意味があります。議論の順序から、それらはすべて完全に関連しているわけではないようです。
icktoofay 2011年

それに加えて、パラメータと同じタイプがたくさんある場合にのみ機能することに同意します。
Timo Willemsen、2011年

これは、質問の最初の例とどのように異なりますか?
セダットカパノグル

まあ、これは.NET Frameworkでも通常の採用スタイルであり、最初の例は、小さな変更可能性の問題が発生したとしても(この例は明らかに別のライブラリからのものです)、それが何をしているのかについて非常に正しいことを示しています。ところでいい質問です。
Teoman Soygul

2
私は時間をかけてこのスタイルにもっと集中してきました。「パラメータが多すぎる」問題のかなりの量は、適切な論理グループと抽象化で解決できるようです。最終的には、コードが読みやすくなり、モジュール化されます。
セダットカパノグル2013

87

ビルダーとドメイン固有の言語スタイルのAPIの組み合わせを使用します--Fluentインターフェイス。APIは少し冗長ですが、インテリセンスを使用すると、入力が非常に速く、理解しやすくなります。

public class Param
{
        public string A { get; private set; }
        public string B { get; private set; }
        public string C { get; private set; }


  public class Builder
  {
        private string a;
        private string b;
        private string c;

        public Builder WithA(string value)
        {
              a = value;
              return this;
        }

        public Builder WithB(string value)
        {
              b = value;
              return this;
        }

        public Builder WithC(string value)
        {
              c = value;
              return this;
        }

        public Param Build()
        {
              return new Param { A = a, B = b, C = c };
        }
  }


  DoSomeAction(new Param.Builder()
        .WithA("a")
        .WithB("b")
        .WithC("c")
        .Build());

+1-この種のアプローチ(私は一般に「流暢なインターフェース」と呼ばれているのを見てきました)も、まさに私が考えていたものです。
Daniel Pryden

+1-これは私が同じ状況になったものです。ただし、クラスは1つだけDoSomeActionで、そのメソッドでした。
Vilx-

+1私もこれを考えましたが、私は流暢なインターフェイスに慣れていないので、ここにぴったり合うかどうかはわかりませんでした。自分の直感を検証してくれてありがとう。
Matt

この質問をする前はビルダーパターンについて聞いたことがなかったので、これは私にとって洞察に満ちた経験でした。どれくらい一般的でしょうか?なぜなら、パターンを聞いたことがない開発者は、ドキュメントがなければ使用法を理解するのに苦労するかもしれません。
セダットカパノグル2011年

1
@ssg、これらのシナリオでは一般的です。たとえば、BCLには接続文字列ビルダークラスがあります。
Samuel Neff、2011年

10

依存関係が多すぎるため、問題のクラスが単一責任の原則に違反しているというかなり確かな兆候があります。これらの依存関係をFacade Dependenciesのクラスターにリファクタリングする方法を探します。


1
私は1つ以上のパラメーターオブジェクトを導入してリファクタリングしますが、すべての引数を単一の不変型に移動すると、何も達成されません。トリックは、より密接に関連している引数のクラスターを探し、それらのクラスターのそれぞれを個別のパラメーターオブジェクトにリファクタリングすることです。
マーク

2
各グループをそれ自体で不変オブジェクトに変えることができます。各オブジェクトは数個のパラメーターのみをとる必要があるため、実際の引数の数は同じままですが、単一のコンストラクターで使用される引数の数は削減されます。
Mark Seemannが

2
+1 @ssg:このようなことを自分で納得させることができるたびに、このレベルのパラメータを必要とする大きなメソッドから有用な抽象化を推進することにより、時間の経過とともに自分が間違っていることを証明しました。EvansによるDDDの本は、これについて考える方法についていくつかのアイデアを与えるかもしれません(システムは、関連する場所からそのようなパターンのアプリケーションに潜在的に遠いように聞こえるかもしれません)-(そして、それはどちらにしても素晴らしい本です)。
Ruben Bartelink

3
@ルーベン:「優れたオブジェクト指向の設計では、クラスに5つ以上のプロパティがあってはならない」という正気の本はありません。クラスは論理的にグループ化され、そのようなコンテキスト化は数量に基づくことはできません。ただし、C#の不変性サポートは、優れたOO設計原則に違反する前に、特定の数のプロパティで問題を引き起こし始めます。
セダットカパノグル2011年

3
@ルーベン:私はあなたの知識、態度、気性を判断しませんでした。同じことを期待しています。私はあなたの推薦が良くないと言っているのではありません。私の問題は、最も完璧なデザインでも発生する可能性があり、それはここで見過ごされているポイントのようです。経験豊富な人々が最も一般的な間違いについて根本的な質問をする理由を私は理解していますが、2、3ラウンド後にそれを明らかにすることはあまり楽しくありません。完璧なデザインでその問題が発生する可能性は非常に高いと私は再び言わなければなりません。そして、賛成票をありがとう!
セダットカパノグル2011年

10

パラメータのデータ構造をaからa classに変更するだけで問題structありません。

public struct DoSomeActionParameters 
{
   public string A;
   public string B;
   public DateTime C;
   public OtherEnum D;
   public string E;
   public string F;
}

public ResultEnum DoSomeAction(DoSomeActionParameters parameters, out Guid code) 

メソッドは、構造の独自のコピーを取得します。引数変数に加えられた変更はメソッドによって監視できず、メソッドが変数に加えた変更は呼び出し元によって監視できません。分離は不変性なしで実現されます。

長所:

  • 実装が最も簡単
  • 基礎となる力学における行動の最小変化

短所:

  • 不変性は明らかではないため、開発者の注意が必要です。
  • 不変性を維持するための不必要なコピー
  • スタックスペースを占有

+1それは本当ですが、「フィールドを直接公開することは悪い」という部分を解決しません。そのAPIでプロパティの代わりにフィールドを使用することを選択した場合、どのように悪いことがさらに悪化するかはわかりません。
セダットカパノグル2011年

@ssg-フィールドではなくパブリックプロパティにします。これをコードを持たない構造体として扱う場合、プロパティとフィールドのどちらを使用しても、大きな違いはありません。コード(検証など)を提供することを決定した場合は、必ずそれらをプロパティにする必要があります。少なくともパブリックフィールドでは、この構造に存在する不変条件についての幻想は誰にもありません。パラメータとまったく同じように、メソッドによって検証する必要があります。
Jeffrey L Whitledge、2011年

そうだね。驚くばかり!ありがとう。
セダットカパノグル2011年

8
公開フィールドは悪であるというルールは、オブジェクト指向設計のオブジェクトに適用されると思います。私が提案している構造体は、パラメーターのベアメタルコンテナーにすぎません。あなたの本能は不変のものを使用することでしたので、そのような基本的なコンテナーはこの機会に適していると思います。
Jeffrey L Whitledge、2011年

1
@JeffreyLWhitledge:公開フィールド構造体が悪であるという考えは本当に嫌いです。そのような主張は、ドライバーは悪であり、ドライバーの先端が釘の頭をへこませるので、人々はハンマーを使用するべきであると言っていることと同等であると私を襲います。釘を打つ必要がある場合はハンマーを使用する必要がありますが、ねじを打つ必要がある場合はドライバーを使用する必要があります。exposed-field構造体がまさにその仕事に適したツールである多くの状況があります。ちなみに、get / setプロパティを持つ構造体が本当に適切なツールである場所はほとんどありません(ほとんどの場合...
supercat

6

データクラス内にビルダークラスを作成するのはどうでしょう。データクラスはすべてのセッターをプライベートとして持ち、ビルダーだけがセッターを設定できます。

public class DoSomeActionParameters
    {
        public string A { get; private set; }
        public string B  { get; private set; }
        public DateTime C { get; private set; }
        public OtherEnum D  { get; private set; }
        public string E  { get; private set; }
        public string F  { get; private set; }

        public class Builder
        {
            DoSomeActionParameters obj = new DoSomeActionParameters();

            public string A
            {
                set { obj.A = value; }
            }
            public string B
            {
                set { obj.B = value; }
            }
            public DateTime C
            {
                set { obj.C = value; }
            }
            public OtherEnum D
            {
                set { obj.D = value; }
            }
            public string E
            {
                set { obj.E = value; }
            }
            public string F
            {
                set { obj.F = value; }
            }

            public DoSomeActionParameters Build()
            {
                return obj;
            }
        }
    }

    public class Example
    {

        private void DoSth()
        {
            var data = new DoSomeActionParameters.Builder()
            {
                A = "",
                B = "",
                C = DateTime.Now,
                D = testc,
                E = "",
                F = ""
            }.Build();
        }
    }

1
+1これは完全に有効なソリューションですが、そのような単純な設計決定を維持するには足場が多すぎると思います。特に、「readonly struct」ソリューションが理想に「非常に」近い場合。
セダットカパノグル2011年

2
これは「パラメータが多すぎる」問題をどのように解決しますか?構文は異なる場合がありますが、問題は同じように見えます。これは批判ではありません。私はこのパターンに慣れていないので、興味があります。
alexD

1
@alexDこれは、関数パラメーターが多すぎてオブジェクトを不変に保つという問題を解決します。プライベートクラスを設定できるのはビルダークラスだけであり、一度パラメーターオブジェクトを取得すると、それを変更することはできません。問題は、多くの足場コードが必要なことです。
marto

5
ソリューションのパラメーターオブジェクトは不変ではありません。ビルダーを保存した人は
ビルド

1
@astefの要点としては、現在記述されているDoSomeActionParameters.Builderように、インスタンスを使用して1つのDoSomeActionParametersインスタンスを作成および構成できます。のプロパティBuild()への後続の変更を呼び出した後Builder元の DoSomeActionParametersインスタンスのプロパティの変更が続行され、への後続の呼び出しBuild()は同じDoSomeActionParametersインスタンスを返し続けます。それは本当にあるべきpublic DoSomeActionParameters Build() { var oldObj = obj; obj = new DoSomeActionParameters(); return oldObj; }です。
BACON、2017

6

私はC#プログラマではありませんが、C#は名前付き引数をサポートしていると思います(F#はそうであり、C#はそのようなものに対して大部分は機能互換です) 。 .aspx#Y342

したがって、元のコードを呼び出すと、次のようになります。

public ResultEnum DoSomeAction( 
 e:"bar", 
 a: "foo", 
 c: today(), 
 b:"sad", 
 d: Red,
 f:"penguins")

これにより、オブジェクトの作成に必要なスペースや考えがなくなり、基礎となるシステムで何が起こっているかをまったく変更していないという、すべての利点があります。引数に名前が付けられていることを示すために何も再コーディングする必要はありません

編集:ここに私が見つけたアーティカルがあります。 http://www.globalnerdy.com/2009/03/12/default-and-named-parameters-in-c-40-sith-lord-in-training/ C#4.0は名前付き引数をサポートしていますが、3.0はそうではありません


確かに改善。しかし、これはコードの読みやすさを解決するだけであり、開発者が名前付きパラメーターの使用をオプトインした場合にのみ解決します。名前を指定するのを忘れてメリットを与えるのは簡単です。コード自体を書くときは役に立ちません。たとえば、関数を小さなものにリファクタリングし、単一の含まれたパケットでデータを渡す場合などです。
セダットカパノグル

6

不変性を強制するインターフェース(つまり、ゲッターのみ)を作成しないのはなぜですか?

これは基本的に最初のソリューションですが、関数にインターフェースを使用してパラメーターにアクセスするように強制します。

public interface IDoSomeActionParameters
{
    string A { get; }
    string B { get; }
    DateTime C { get; }
    OtherEnum D { get; }
    string E { get; }
    string F { get; }              
}

public class DoSomeActionParameters: IDoSomeActionParameters
{
    public string A { get; set; }
    public string B { get; set; }
    public DateTime C { get; set; }
    public OtherEnum D { get; set; }
    public string E { get; set; }
    public string F { get; set; }        
}

そして関数宣言は次のようになります:

public ResultEnum DoSomeAction(IDoSomeActionParameters parameters, out Guid code)

長所:

  • のようなスタックスペースの問題はありません structソリューションの
  • 言語のセマンティクスを使用した自然な解決策
  • 不変性は明らかです
  • 柔軟(消費者は必要に応じて別のクラスを使用できます)

短所:

  • 一部の反復作業(2つの異なるエンティティで同じ宣言)
  • 開発者は、これをDoSomeActionParametersマッピングできるクラスであると推測する必要がありますIDoSomeActionParameters

+1どうしてそんなことを考えなかったのかわかりませんか?:)私はオブジェクトがまだあまりにも多くのパラメータを持つコンストラクタの影響を受けると思いましたが、それは事実ではありません。はい、それも非常に有効なソリューションです。私が考えることができる唯一の問題は、APIユーザーが特定のインターフェイスをサポートする適切なクラス名を見つけることは本当に簡単ではないということです。最小限のドキュメントを必要とするソリューションの方が優れています。
セダットカパノグル

私はこれが好きです。マップの複製と知識はリシャープで処理され、具象クラスのデフォルトコンストラクターを使用してデフォルト値を提供できます
Anthony Johnston

2
私はそのアプローチが好きではありません。の任意の実装への参照があり、IDoSomeActionParametersその中に値をキャプチャしたい人は、参照を保持するだけで十分かどうか、または値を他のオブジェクトにコピーする必要があるかどうかを知る方法がありません。読み取り可能なインターフェイスは、状況によっては役立ちますが、不変のものにする手段としては役立ちません。
スーパーキャット2012

「IDoSomeActionParametersパラメータ」をDoSomeActionParametersにキャストして変更できます。開発者は、パラメータを互換性のあるものにしようとする試みを回避していることにさえ気付かないかもしれません
ジョセフシンプソン

3

これは古い質問であることはわかっていますが、同じ問題を解決しなければならなかったので、私の提案を聞いてみようと思いました。さて、ユーザーがこのオブジェクトを自分で構築できないようにしたくないという追加の要件があったので、確かに私の問題はあなたの問題とは少し異なりました(データのすべてのハイドレーションはデータベースからのものなので、すべての構築を内部で投獄することができました)。これにより、プライベートコンストラクターと次のパターンを使用できました。

    public class ExampleClass
    {
        //create properties like this...
        private readonly int _exampleProperty;
        public int ExampleProperty { get { return _exampleProperty; } }

        //Private constructor, prohibiting construction outside of this class
        private ExampleClass(ExampleClassParams parameters)
        {                
            _exampleProperty = parameters.ExampleProperty;
            //and so on... 
        }

        //The object returned from here will be immutable
        public ExampleClass GetFromDatabase(DBConnection conn, int id)
        {
            //do database stuff here (ommitted from example)
            ExampleClassParams parameters = new ExampleClassParams()
            {
                ExampleProperty = 1,
                ExampleProperty2 = 2
            };

            //Danger here as parameters object is mutable

            return new ExampleClass(parameters);    

            //Danger is now over ;)
        }

        //Private struct representing the parameters, nested within class that uses it.
        //This is mutable, but the fact that it is private means that all potential 
        //"damage" is limited to this class only.
        private struct ExampleClassParams
        {
            public int ExampleProperty { get; set; }
            public int AnotherExampleProperty { get; set; }
            public int ExampleProperty2 { get; set; }
            public int AnotherExampleProperty2 { get; set; }
            public int ExampleProperty3 { get; set; }
            public int AnotherExampleProperty3 { get; set; }
            public int ExampleProperty4 { get; set; }
            public int AnotherExampleProperty4 { get; set; } 
        }
    }

2

DoSomeActionメソッドの複雑さに応じて、これは少し重いかもしれませんが、ビルダースタイルのアプローチを使用できます。これらの線に沿った何か:

public class DoSomeActionParametersBuilder
{
    public string A { get; set; }
    public string B { get; set; }
    public DateTime C { get; set; }
    public OtherEnum D { get; set; }
    public string E { get; set; }
    public string F { get; set; }

    public DoSomeActionParameters Build()
    {
        return new DoSomeActionParameters(A, B, C, D, E, F);
    }
}

public class DoSomeActionParameters
{
    public string A { get; private set; }
    public string B { get; private set; }
    public DateTime C { get; private set; }
    public OtherEnum D { get; private set; }
    public string E { get; private set; }
    public string F { get; private set; }

    public DoSomeActionParameters(string a, string b, DateTime c, OtherEnum d, string e, string f)
    {
        A = a;
        // etc.
    }
}

// usage
var actionParams = new DoSomeActionParametersBuilder
{
    A = "value for A",
    C = DateTime.Now,
    F = "I don't care for B, D and E"
}.Build();

result = foo.DoSomeAction(actionParams, out code);

ああ、マルトはビルダーの提案に私を倒しました!
クリスホワイト

2

マンジ応答に加えて-1つの操作をいくつかの小さな操作に分割することもできます。比較:

 BOOL WINAPI CreateProcess(
   __in_opt     LPCTSTR lpApplicationName,
   __inout_opt  LPTSTR lpCommandLine,
   __in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
   __in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
   __in         BOOL bInheritHandles,
   __in         DWORD dwCreationFlags,
   __in_opt     LPVOID lpEnvironment,
   __in_opt     LPCTSTR lpCurrentDirectory,
   __in         LPSTARTUPINFO lpStartupInfo,
   __out        LPPROCESS_INFORMATION lpProcessInformation
 );

そして

 pid_t fork()
 int execvpe(const char *file, char *const argv[], char *const envp[])
 ...

POSIXを知らない人にとっては、子供の作成は次のように簡単です。

pid_t child = fork();
if (child == 0) {
    execl("/bin/echo", "Hello world from child", NULL);
} else if (child != 0) {
    handle_error();
}

それぞれの設計の選択は、実行できる操作のトレードオフを表しています。

PS。はい-ビルダーに似ています-逆の場合のみ(つまり、呼び出し側ではなく呼び出し先側)。この特定のケースでは、ビルダーよりも優れている場合とそうでない場合があります。


これは小さな操作ですが、単一責任の原則に違反しているすべての人に適しています。私の質問は、他のすべてのデザイン改善が行われた直後に行われます。
セダットカパノグル

UPS。申し訳ありませんが、編集前に質問を準備し、後で投稿しました。そのため、気づきませんでした。
Maciej Piechotka

2

これはMikeysとは少し異なりますが、私がやろうとしていることは、全体をできるだけ少なく書くことです

public class DoSomeActionParameters
{
    readonly string _a;
    readonly int _b;

    public string A { get { return _a; } }

    public int B{ get { return _b; } }

    DoSomeActionParameters(Initializer data)
    {
        _a = data.A;
        _b = data.B;
    }

    public class Initializer
    {
        public Initializer()
        {
            A = "(unknown)";
            B = 88;
        }

        public string A { get; set; }
        public int B { get; set; }

        public DoSomeActionParameters Create()
        {
            return new DoSomeActionParameters(this);
        }
    }
}

DoSomeActionParametersは不変であり、デフォルトのコンストラクターはプライベートなので、直接作成することはできません。

初期化子は不変ではなく、トランスポートのみです

使用方法は、初期化子の初期化子を利用しています(ドリフトが発生した場合)。また、初期化子のデフォルトコンストラクターにデフォルトを設定できます。

DoSomeAction(new DoSomeActionParameters.Initializer
            {
                A = "Hello",
                B = 42
            }
            .Create());

ここではパラメーターはオプションですが、必要な場合は、初期化子のデフォルトコンストラクターに配置できます。

検証はCreateメソッドで行うことができます

public class Initializer
{
    public Initializer(int b)
    {
        A = "(unknown)";
        B = b;
    }

    public string A { get; set; }
    public int B { get; private set; }

    public DoSomeActionParameters Create()
    {
        if (B < 50) throw new ArgumentOutOfRangeException("B");

        return new DoSomeActionParameters(this);
    }
}

だから今は

DoSomeAction(new DoSomeActionParameters.Initializer
            (b: 42)
            {
                A = "Hello"
            }
            .Create());

まだ少しは知っていますが、とにかくやってみます

編集:パラメータオブジェクトのcreateメソッドをstaticに移動し、イニシャライザを渡すデリゲートを追加すると、呼び出しから不自然さがいくらか取り除かれます

public class DoSomeActionParameters
{
    readonly string _a;
    readonly int _b;

    public string A { get { return _a; } }
    public int B{ get { return _b; } }

    DoSomeActionParameters(Initializer data)
    {
        _a = data.A;
        _b = data.B;
    }

    public class Initializer
    {
        public Initializer()
        {
            A = "(unknown)";
            B = 88;
        }

        public string A { get; set; }
        public int B { get; set; }
    }

    public static DoSomeActionParameters Create(Action<Initializer> assign)
    {
        var i = new Initializer();
        assign(i)

        return new DoSomeActionParameters(i);
    }
}

通話は次のようになります

DoSomeAction(
        DoSomeActionParameters.Create(
            i => {
                i.A = "Hello";
            })
        );

1

構造を使用しますが、パブリックフィールドの代わりに、パブリックプロパティがあります。

•誰もが(FXCopとJon Skeetを含む)パブリックフィールドを公開することは悪いことであることに同意します。

フィールドではなくプロパティを公開しているため、JonとFXCopは満足のいくものになります。

•Eric Lippertらは、不変性を読み取り専用フィールドに依存することはうそだと言います。

プロパティを使用すると、値が一度だけ設定されていることを確認できるため、エリックは満足します。

    private bool propC_set=false;
    private date pC;
    public date C {
        get{
            return pC;
        }
        set{
            if (!propC_set) {
               pC = value;
            }
            propC_set = true;
        }
    }

1つの半不変オブジェクト(値は設定できますが変更できません)。値型と参照型で機能します。


+1おそらく、読み取り専用のプライベートフィールドとゲッター専用のパブリックプロパティを構造体で組み合わせて、より冗長なソリューションを実現できます。
セダットカパノグル

変化thisする構造のパブリックな読み取り/書き込みプロパティは、パブリックフィールドよりもはるかに悪です。値を1度設定するだけで「問題ない」と思う理由がわかりません。構造体のデフォルトのインスタンスから開始した場合thisでも、プロパティの変更に関連する問題は存在します。
スーパーキャット2012

0

同じ問題が発生したときにプロジェクトで使用したサミュエルの回答の変形:

class MagicPerformer
{
    public int Param1 { get; set; }
    public string Param2 { get; set; }
    public DateTime Param3 { get; set; }

    public MagicPerformer SetParam1(int value) { this.Param1 = value; return this; }
    public MagicPerformer SetParam2(string value) { this.Param2 = value; return this; }
    public MagicPerformer SetParam4(DateTime value) { this.Param3 = value; return this; }

    public void DoMagic() // Uses all the parameters and does the magic
    {
    }
}

そして使用するには:

new MagicPerformer().SeParam1(10).SetParam2("Yo!").DoMagic();

私の場合、セッターメソッドはすべての可能な組み合わせを許可せず、それらの一般的な組み合わせを公開しただけなので、パラメーターは意図的に変更可能でした。それは、私のパラメーターの一部がかなり複雑で、考えられるすべてのケースに対するメソッドの記述が困難で不必要だったからです(クレイジーな組み合わせはめったに使用されません)。


これは変更可能なクラスです。
セダットカパノグル

@ssg-そうですね、そうです。DoMagicそれはオブジェクトを変更しないことにかかわらず、その契約であります。しかし、それは偶発的な変更から保護しないと思います。
Vilx- 2011
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.