ラムダとデリゲートによるリファクタリング
-
03-07-2019 - |
質問
VS2008をインストールしたばかりで、ラムダまたはデリゲート(またはその組み合わせ)で解決できると確信している問題に遭遇しました。
private string ReadData(TcpClient s, string terminator)
{
// Reads a byte steam into a string builder until either data is unavailable or the terminator has not been reached
var sb = new StringBuilder();
do
{
var numBytesRead = s.GetStream().Read(byteBuff, 0, byteBuff.Length);
sb.AppendFormat("{0}", Encoding.ASCII.GetString(byteBuff, 0, numBytesRead));
} while (s.GetStream().DataAvailable && !sb.ToString().Contains(terminator));
return sb.ToString();
}
問題は、文字列に2つの異なる値のいずれかが含まれているかどうかを確認する必要がある場合があることです。 3つの値を確認する必要がある場合があります。
つまり、私が提案するのは、変更することです" !sb.ToString()。Contains(terminator)"メソッドに渡される関数に。
次のようなさまざまな関数を書くことができます:
private bool compare1(string s, string t) {
return s.contains(t)
}
private bool compare2(string s, string t1, string t2) {
return (s.compare(t1) or s.compare(t2)
}
// etc...
次に、3つの異なる値と比較したい場合、これらの関数のいずれかにデリゲートを作成し、それをReadData()メソッドに渡します。
デリゲートに関しては非常に無知であり、これがラムダにとって適切な場所のように思えるかもしれませんが、それが何かを教えてくれます。
呼び出しコードはこれです:
// Enter username .
if (HasData(s,"login:"))
SendData(s, switchUser + TelnetHelper.CRLF);
HasDataはReadDataと同じですが、文字列の代わりにブール値を返します(いくつかのトリックを使用して1つのメソッドにファクタリングしたい-しかし、それは二次的な質問です-でも気軽に答えてください。
参照用:
private bool HasData(TcpClient s, string terminator)
{
// Reads a byte steam into a string builder until either data is unavailable or the terminator has not been reached
var sb = new StringBuilder();
do
{
var numBytesRead = s.GetStream().Read(byteBuff, 0, byteBuff.Length);
sb.AppendFormat("{0}", Encoding.ASCII.GetString(byteBuff, 0, numBytesRead));
} while (s.GetStream().DataAvailable && !sb.ToString().Contains(terminator));
return sb.ToString().Contains(terminator);
}
解決
述語関数を探しているようです。チェックをハードコーディングする代わりに、チェックを実行できるよりもデリゲートをパラメーターとして使用します
private string ReadData(TcpClient s, Func<string,bool> predicate)
{
// Reads a byte steam into a string builder until either data is unavailable or the terminator has not been reached
var sb = new StringBuilder();
do
{
var numBytesRead = s.GetStream().Read(byteBuff, 0, byteBuff.Length);
sb.AppendFormat("{0}", Encoding.ASCII.GetString(byteBuff, 0, numBytesRead));
} while (s.GetStream().DataAvailable && !predicate(sb));
return sb.ToString();
}
その後、適切なデリゲートを作成して渡すだけのラッパーをいくつか作成できます
public bool HasData(TcpClient c, string terminator) {
return HasData(c, (s) => s.Contains(terminator));
}
public bool HasData(TcpClient c, string t1, string t2) {
return HasData(c, (s) => s.Contains(t1) || s.Contains(t2));
}
任意の数のターミネータに基づいて、その場でデリゲートを構築することもできます
public bool HasData(TcpClient c, params string[] terminatorList) {
return HasData(c, (s) => terminatorList.Where(x => s.Contains(x)).Any());
}
他のヒント
1つのオプションは、ReadData()メソッドをオーバーロードして、チェックする値を含む文字列配列を取得することです。 拡張メソッドを使用して、Contains()を拡張して文字列配列。
ReadData()メソッドは次のようになります:
private string ReadData(TcpClient s, string[] terminators) {
// Reads a byte steam into a string builder until either data is unavailable or the terminator has not been reached
var sb = new StringBuilder();
do
{
var numBytesRead = s.GetStream().Read(byteBuff, 0, byteBuff.Length);
sb.AppendFormat("{0}", Encoding.ASCII.GetString(byteBuff, 0, numBytesRead));
} while (s.GetStream().DataAvailable && !sb.ToString().Contains(terminators));
return sb.ToString();
}
Contains()メソッドの拡張子は次のとおりです。
public static bool Contains ( this String str , String[] testValues )
{
foreach ( var value in testValues )
{
if ( str.Contains( value ) )
return true;
}
return false;
}
この実装により、テストする文字列の数が異なるたびに新しい述語を作成する必要がなくなります。
ラムダの構文は自分自身(およびチームの他のメンバー)にとってはやや異質なので、やや異なる解決策を採用することになりました。上記の.Any()関数から変更した場合、.All()の構文を理解できませんでした。
リスト内のすべてのターミネーターが確実に見つかるように、.All()関数も必要でした。だから私は次のようなもので行きました:
delegate bool Predicate (string s, params [] string terminators);
bool HasAll(string s, params string [] terminators) {
foreach (var t in terminators) {
if (!s.contains(t)) return false;
}
return true;
}
bool HasAny(string s, params string [] terminators) {
foreach (var t in terminators) {
if (s.contains(t)) return true;
}
return false;
}
// Just looking now, I could also pass in a bool to switch between the two and remove one of these functions. But this is fairly clear
string ReadData(TcpClient sock, Function predicate, params [] string terminators) {
var sb = new StringBuilder();
do
{
var numBytesRead = s.GetStream().Read(byteBuff, 0, byteBuff.Length);
sb.AppendFormat("{0}", Encoding.ASCII.GetString(byteBuff, 0, numBytesRead));
} while (s.GetStream().DataAvailable && !predicate(sb.ToString(), terminators);
return sb.ToString();
}
その後、呼び出しコードは次のようになります。
private void someFunc()
{
Predicate any = new Predicate(HasAny);
Predicate all = new Predicate(HasAll);
String response;
// Check all strings exist
response = ReadData(this.sock, all, "(", ")", "->")
if (all(response, "(", ")", "->")
SendData(this.sock, ...);
// Check any string exists
response = ReadData(this.sock, any, "Hi", "Hey", "Hello");
if (any(response, "Hi", "Hey", "Hello"))
SendData(this.sock, ...);
}
おそらくHas [Any | All]関数にnullチェックを追加し、do..whileをしばらく逆にして、paramsを複製する代わりにresponse!= nullをチェックします。このソリューションは、私のすべてのユースケースに適しており、かなり人間が読めると思います。上記の小さな変更を行う限り、
このすべてが私にとってラムダ式を学ぶ必要があることを強調しています!