閉鎖の濫用?さまざまな原則の違反?またはOK?
-
27-09-2019 - |
質問
編集:いくつかの構文と一貫性の問題を修正して、コードをもう少し明らかにし、実際に行っていることに近づきました。
私は次のようないくつかのコードを持っています:
SomeClass someClass;
var finalResult =
DoSomething(() =>
{
var result = SomeThingHappensHere();
someClass = result.Data;
return result;
})
.DoSomething(() => return SomeOtherThingHappensHere(someClass))
.DoSomething(() => return AndYetAnotherThing())
.DoSomething(() => return AndOneMoreThing(someClass))
.Result;
HandleTheFinalResultHere(finalResult);
どこ DoSomething
メソッドは拡張メソッドであり、FUNCが渡されると予想されます。したがって、各メソッドはそれぞれのdosomeThe => lambdaの結果タイプを返します。
これはaに似ています 多分モナド. 。ヌルをチェックする代わりに、結果クラスのステータスをチェックしています。
私が直面している問題は、私のコードにこの種の構成を持ちたいということですが、構成された呼び出し結果の1つからデータを別のコールに渡すことができる必要があります。 someClass
変数。
私の質問は、これが技術的に正しいかどうかではありません...私は現在それをやっているので、私はこれがうまくいくことを知っています。私の質問は、これが閉鎖の濫用、またはコマンドクエリの分離、または他の原則であるかどうか、そしてこの状況を処理するためにどんなより良いパターンがあるかを尋ねることです。今、このタイプのコードで「光沢のある新しいハンマー」モードで立ち往生しています。
解決
すでに指摘されているように、あなたはここでモナドをほとんど実装しました。
あなたのコードは、ラムダが副作用を持っているという点で少し不可能です。モナドはこれをよりエレガントに解決します。
それで、あなたのコードを適切なモナドに変えてみませんか?
ボーナス:LINQ構文を使用できます!
私が提示する:
結果へのlinq
例:
var result =
from a in SomeThingHappensHere()
let someData = a.Data
from b in SomeOtherThingHappensHere(someData)
from c in AndYetAnotherThing()
from d in AndOneMoreThing(someData)
select d;
HandleTheFinalResultHere(result.Value);
と 結果へのlinq, 、これは最初に実行されます SomeThingHappensHere
. 。それが成功した場合、それは Data
結果と実行のプロパティ SomeOtherThingHappensHere
. 。それが成功した場合、それは実行されます AndYetAnotherThing
, 、 等々。
ご覧のとおり、簡単に操作をチェーンして、以前の操作の結果を参照できます。各操作は次々に実行され、エラーが発生したときに実行が停止します。
from x in
ビット各行は少しうるさいですが、IMOは、これよりも同等の複雑さの方が読みやすくなります!
これをどのように機能させるのですか?
C#のモナドは3つの部分で構成されています。
タイプ 何か -,
Select
/SelectMany
そのための拡張方法、およびaを変換する方法 t に 何か -.
あなたがする必要があるのは、モナドのように見えるもの、モナドのように感じ、モナドのような匂いがするものを作成することです。
の種類と方法 結果へのlinq 以下の通り。
結果u003CT>タイプ:
結果を表す簡単なクラス。結果は、タイプの値です t, 、またはエラー。結果はaから構築できます t またはansから 例外.
class Result<T>
{
private readonly Exception error;
private readonly T value;
public Result(Exception error)
{
if (error == null) throw new ArgumentNullException("error");
this.error = error;
}
public Result(T value) { this.value = value; }
public Exception Error
{
get { return this.error; }
}
public bool IsError
{
get { return this.error != null; }
}
public T Value
{
get
{
if (this.error != null) throw this.error;
return this.value;
}
}
}
拡張方法:
の実装 Select
と SelectMany
方法。メソッドシグネチャはC#仕様に記載されているため、心配する必要があるのは実装だけです。これらは、すべてのメソッド引数を意味のある方法で組み合わせようとすると、非常に自然になります。
static class ResultExtensions
{
public static Result<TResult> Select<TSource, TResult>(this Result<TSource> source, Func<TSource, TResult> selector)
{
if (source.IsError) return new Result<TResult>(source.Error);
return new Result<TResult>(selector(source.Value));
}
public static Result<TResult> SelectMany<TSource, TResult>(this Result<TSource> source, Func<TSource, Result<TResult>> selector)
{
if (source.IsError) return new Result<TResult>(source.Error);
return selector(source.Value);
}
public static Result<TResult> SelectMany<TSource, TIntermediate, TResult>(this Result<TSource> source, Func<TSource, Result<TIntermediate>> intermediateSelector, Func<TSource, TIntermediate, TResult> resultSelector)
{
if (source.IsError) return new Result<TResult>(source.Error);
var intermediate = intermediateSelector(source.Value);
if (intermediate.IsError) return new Result<TResult>(intermediate.Error);
return new Result<TResult>(resultSelector(source.Value, intermediate.Value));
}
}
結果を自由に変更できますu003CT>より複雑なルールを実装するためのクラスと拡張メソッド。拡張メソッドの署名のみが記載されているとおりでなければなりません。
他のヒント
ここでモナドに非常によく似たものを作ったように、私には見えます。
デリゲートタイプAを作成することで、適切なモナドにすることができます Func<SomeClass, SomeClass>
, 、初期をセットアップする方法があります SomeClass
渡す価値、そして持っている DoSomething
次のパラメーターとして1つのリターン値を渡す - これにより、字句的にスコープされた共有状態に依存するのではなく、チェーンを明示的にすることになります。
このコードの弱点は、第1ラムダと2番目のラムダの間の暗黙の結合です。修正する最良の方法はわかりません。