新しいオブジェクトに割り当てるときのスイッチまたは辞書


12

最近、ステートメントのDictionaries代わりに1-1の関係をマッピングすることを好むようになりましたSwitch。書くのが少し速く、精神的に処理するのが簡単だと思います。残念ながら、オブジェクトの新しいインスタンスにマッピングするとき、次のように定義したくありません。

var fooDict = new Dictionary<int, IBigObject>()
{
    { 0, new Foo() }, // Creates an instance of Foo
    { 1, new Bar() }, // Creates an instance of Bar
    { 2, new Baz() }  // Creates an instance of Baz
}

var quux = fooDict[0]; // quux references Foo

そのコンストラクトを考えると、CPUサイクルとメモリを無駄にして3つのオブジェクトを作成し、コンストラクターに含まれている可能性のある処理をすべて実行し、そのうちの1つだけを使用することになりました。またfooDict[0]、この場合、他のオブジェクトをマッピングするとFoo、意図したとおりの新しいインスタンスを作成するのではなく、同じものを参照することになります。解決策は、代わりにラムダを使用することです:

var fooDict = new Dictionary<int, Func<IBigObject>>()
{
    { 0, () => new Foo() }, // Returns a new instance of Foo when invoked
    { 1, () => new Bar() }, // Ditto Bar
    { 2, () => new Baz() }  // Ditto Baz
}

var quux = fooDict[0](); // equivalent to saying 'var quux = new Foo();'

これはあまりにも混乱を招くポイントになっていますか?()最後に見逃すのは簡単です。または、関数/式へのマッピングはかなり一般的な習慣ですか?別の方法は、スイッチを使用することです。

IBigObject quux;
switch(someInt)
{
    case 0: quux = new Foo(); break;
    case 1: quux = new Bar(); break;
    case 2: quux = new Baz(); break;
}

どの呼び出しがより受け入れられますか?

  • 辞書、より高速な検索とより少ないキーワード(大文字と小文字の区別)
  • スイッチ:コードでより一般的に見られますが、間接指定にFunc <>オブジェクトを使用する必要はありません。

2
ラムダがなければ、同じキーでルックアップを行うたびに同じインスタンスが返されます(のようにfooDict[0] is fooDict[0])。ラムダとスイッチの両方では、そうではありません
ラチェットフリーク

@ratchetfreakはい、サンプルを入力しているときに実際にこれを実現しました。どこかにメモしたと思います。
KChaloux

1
明示的に定数に入れたという事実は、作成されたオブジェクトが可変である必要があることを意味すると思います。しかし、いつかそれらを不変にすることができる場合、オブジェクトを直接返すことが最良の解決策になります。dictをconstフィールドに入れて、アプリケーション全体で作成コストを1回だけ負担できます。
ローランブルゴーロイ14

回答:


7

これは、ファクトリーパターンに関する興味深い見解です。辞書とラムダ式の組み合わせが好きです。そのコンテナを新しい方法で見るようになりました。

CPUサイクルに関する質問で、非ラムダアプローチでは必要なものが提供されないというコメントで述べた懸念を無視しています。

私はどちらのアプローチ(スイッチ対辞書+ラムダ)がうまくいくと思います。唯一の制限は、ディクショナリを使用することで、返されるクラスを生成するために受け取ることができる入力のタイプを制限することです。

switchステートメントを使用すると、入力パラメーターの柔軟性が向上します。ただし、これが問題になる場合は、Dictionaryをメソッド内にラップして同じ結果を得ることができます。

チームにとって新しいものである場合は、コードにコメントして、何が起こっているのかを説明してください。チームコードのレビューを依頼し、行われたことを説明し、それを認識させます。それ以外は、見栄えがいいです。


残念ながら、約1か月前の時点で、私のチームは私だけで構成されています(リードは辞めました)。工場のパターンとの関連性は考えていませんでした。それは実際にはきちんとした観察です。
KChaloux

1
@KChaloux:あなただけのファクトリメソッドパターンを使用していた場合はもちろん、あなたがcase 0: quux = new Foo(); break;なり、case 0: return new Foo();率直に書きやすいと限りより読みやすくなる{ 0, () => new Foo() }
PDR

@pdrこれはすでにコードのいくつかの場所に現れています。おそらく、この質問に影響を与えたオブジェクトにファクトリメソッドを作成するのには十分な理由がありますが、それだけで質問するほど面白いと思いました。
KChaloux

1
@KChaloux:私は、スイッチ/ケースを辞書に置き換えることに最近執着しているとは思いません。独自の方法でスイッチを単純化して分離しても効果的でないケースはまだありません。
pdr

@pdr Obsessionは、ここで使用する強力な言葉です。値間の1回限りのマッピングの処理方法を決定する際の考慮事項の詳細。繰り返される場合には、創造的な方法を分離するのが最善であることに同意します。
-KChaloux

7

C#4.0はLazy<T>、独自の2番目のソリューションに似たクラスを提供しますが、「遅延初期化」をより明示的に叫びます。

var fooDict = new Dictionary<int, Lazy<IBigObject>>()
{
    { 0, new Lazy(() => new Foo()) }, // Returns a new instance of Foo when invoked
    { 1, new Lazy(() => new Bar()) }, // Ditto Bar
    { 2, new Lazy(() => new Baz()) }  // Ditto Baz
}

ああ、私はそれを知りませんでした。
KChaloux

良いですね!
ローランブルゴーロイ14

2
ただし、Lazy.Valueが呼​​び出されると、その有効期間中は同じインスタンスが使用されます。参照してください遅延初期化
ダン・リヨン

もちろん、そうでない場合は、遅延初期化ではなく、毎回再初期化するだけです。
アヴナーシャハルカシュタン14

OPは、毎回新しいインスタンスを作成するために必要であると述べています。ラムダを使用する2番目のソリューションとスイッチを使用する3番目のソリューションは両方ともこれを行いますが、最初のソリューションとLazy <T>実装はしません。
ダンライオンズ14

2

文体的には、読みやすさは両者で等しいと思います。を使用して依存関係の注入を行う方が簡単Dictionaryです。

を使用するときにキーが存在するかどうかを確認し、存在Dictionaryしない場合はフォールバックを提供する必要があることを忘れないでください。

switch静的コードパスのステートメントと、Dictionary動的コードパス(エントリを追加または削除できる場所)のステートメントを好むでしょう。コンパイラは、静的な最適化を実行できますがswitchDictionaryます。

興味深いことに、DictionaryPythonにはswitchステートメントがないため、このパターンはPythonで時々行われるものです。それ以外の場合、if-elseチェーンを使用します。


1

一般的に、私はどちらも好まないでしょう。

これを消費するものは何でも動作しますFunc<int, IBigObject>。その後、マッピングのソースは、ディクショナリ、またはswitchステートメント、Webサービスコール、または何らかのファイルルックアップを含むメソッドになります。

実装に関しては、「ハードコードディクショナリ、ルックアップキー、結果を返す」から「ファイルからディクショナリをロードする、ルックアップキー、結果を返す」より簡単にリファクタリングできるため、ディクショナリを選択します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.