Refactoring con Lambda e delegati
-
03-07-2019 - |
Domanda
Ho appena installato VS2008 e ho riscontrato un problema che sono sicuro che possa essere risolto con lambda o delegati (o una combinazione!).
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();
}
Il problema è che a volte devo verificare se la stringa contiene uno di due valori diversi. A volte potrei aver bisogno di controllarlo per tre valori.
Quindi quello che propongo è cambiare " !. Sb.ToString () Contiene (Terminator) " a una funzione che viene passata nel metodo
Potrei scrivere le mie diverse funzioni come:
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...
Quindi, quando voglio confrontare 3 diversi valori, creare un delegato a una di queste funzioni, quindi passarlo al metodo ReadData ().
Sono molto all'oscuro quando si tratta di delegati, e non sono sicuro se questo sembra il posto giusto per un lambda ma qualcosa mi sta dicendo che lo è.
Il codice chiamante è questo:
// Enter username .
if (HasData(s,"login:"))
SendData(s, switchUser + TelnetHelper.CRLF);
HasData è identico a ReadData, ma restituisce un valore bool invece che una stringa (che vorrei anche includere in un metodo usando qualche trucco - ma questa è una domanda secondaria - sentiti libero di rispondere però.
Solo per riferimento:
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);
}
Soluzione
Sembra che tu stia cercando una funzione predicata. Invece di codificare a fondo il controllo, prendi un delegato come parametro che può fare il controllo
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();
}
Quindi puoi creare diversi wrapper che creano semplicemente il delegato appropriato e lo passano
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));
}
Puoi persino creare un delegato al volo in base al numero arbitrario di terminatori
public bool HasData(TcpClient c, params string[] terminatorList) {
return HasData(c, (s) => terminatorList.Where(x => s.Contains(x)).Any());
}
Altri suggerimenti
Un'opzione sarebbe quella di sovraccaricare il metodo ReadData () per acquisire una matrice di stringhe contenente i valori che si stanno verificando. Utilizzando un metodo di estensione , puoi estendere Contains () per prendere un array di stringhe.
Il tuo metodo ReadData () potrebbe essere:
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();
}
L'estensione del metodo Contains () potrebbe essere:
public static bool Contains ( this String str , String[] testValues )
{
foreach ( var value in testValues )
{
if ( str.Contains( value ) )
return true;
}
return false;
}
Questa implementazione elimina la necessità di creare un nuovo predicato ogni volta che si dispone di un numero diverso di stringhe da testare.
Poiché la sintassi degli lambda è in qualche modo estranea a me stessa (e al resto della mia squadra), ho finito con una soluzione leggermente diversa. Non sono riuscito a capire la sintassi di .All () quando modificato dalla funzione .Any () sopra.
Avevo bisogno anche di una funzione .All (), per assicurarmi di trovare tutti i terminatori nell'elenco. Quindi ho finito con qualcosa di simile al seguente:
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();
}
Quindi il codice chiamante appare come:
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, ...);
}
Probabilmente aggiungerò i controlli null nelle funzioni Has [Any | All], invertirò il do..while per un po 'e controllerò semplicemente response! = null invece di duplicare i parametri. Questa soluzione si adatta a tutti i miei casi d'uso ed è abbastanza leggibile dall'uomo, credo. Fintanto che apporto le piccole modifiche di cui ho appena parlato.
Tutta questa faccenda sottolinea per me il mio bisogno di imparare le espressioni lambda!