Come chiamare Dictionary .TryGetValue () dove K: Predicate , V: enum
-
29-09-2019 - |
Domanda
Ho Dictionary<Predicate<double>, SomeEnum>
:
var dic = new Dictionary<Predicate<double>, SomeEnum>
{
{ (d) => d < 10, SomeEnum.Foo },
{ (d) => d > 90, SomeEnum.Bar }
};
Voglio chiamare TryGetValue(K, out V)
contro di essa in questo modo:
dic.TryGetValue(99)
e ricevere
SomeStruct.Bar
Ma prima param per TryGetValue()
è Predicate<T>
, non solo T
. Come posso fare quello che voglio?
Ho trovato solo una soluzione sporca:
var kpv = dic.FirstOrDefault(p => p.Key(99));
if (kpv.Key != null)
var result = kpv.Value;
Ci sono altri modi?
o come attuare la mia idea correttamente -?. Dichiarare una chiave non come una costante, ma come un segmento
Soluzione
Ci sono un paio di cose sbagliate qui:
Predicate<double>
non è un tipo adatto da utilizzare come TKey
. La chiave per un dizionario si suppone che individuare un valore, non calcolare un valore.
Questo non avrebbe alcun senso utilizzando lambda sia. Perché sono anonimi, non si ottiene alcuna equivalenza, e non sarà in grado di utilizzare un dizionario.
Vedere questo esempio di codice per un esempio:
Predicate<double> fn_1 = d => d == 34.0d;
Predicate<double> fn_2 = d => d == 34.0d;
// Note: There are not equal
if (fn_1 == fn_2)
Console.WriteLine("These are Equal?");
Se non altro, è possibile utilizzare un elenco di delegati ed eseguire a ciascuno di trovare quelli che incontro, ma a quel punto si deve aspettare più risultati. Se si desidera solo per ottenere un unico risultato, allora è necessario considerare che ordine i predicati sono memorizzate all'interno della vostra lista.
Do not abuso KeyValuePair
come un hack per non avere Tuple<T1,T2>
. Sarebbe abbastanza facile creare una classe che ha sia un predicato ed un SomeStruct. Guardate:
public class MySegment
{
public Predicate<double> Predicate {get;set;}
public SomeStruct Result {get;set;}
}
Per passare attraverso una sequenza di predicati, e trovare quelli corrispondenti sarebbe simile a questa:
...
List<MySegment> list = new List<MySegment>();
...
list.Add(new MySegment { Predicate = d => d < 10, Result = SomeStruct.Foo });
list.Add(new MySegment { Predicate = d => d > 90, Result = SomeStruct.Bar });
...
public IEnumerable<SomeStruct> GetResults(double input)
{
foreach (var item in list)
if (item.Predicate(input))
yield return item.Result;
}
Altri suggerimenti
Se l'elenco dei predicati non è troppo lungo, si può semplicemente aggiungerli a una List<KeyValuePair<Predicate<T>, V>>
e quindi si esegue una query LINQ:
var lt10 = new KeyValuePair<Predicate<Double>, SomeStruct>(d => d < 10, SomeStruct.Foo);
var gt90 = new KeyValuePair<Predicate<Double>, SomeStruct>(d => d > 90, SomeStruct.Bar);
var predicates = new List<KeyValuePair<Predicate<Double>, SomeStruct>>() { lt10, gt90 };
var result = predicates.FirstOrDefault(p => p.Key(99));
È meglio utilizzare SomeStruct?
invece di SomeStruct
, inoltre, da allora FirstOrDefault
darà un risultato inequivocabile se non corrisponde a nessuna.
Se l'elenco è molto lungo, si vuole prendere in considerazione un qualche tipo di struttura dati che permette di query su una serie, come un Interval Albero .
Questo non può essere fatto utilizzando un dizionario, perché si basa su valori hash per determinare rapidamente dove cercare un determinato tasto.
Come hai scoperto, è possibile richiamare i predicati direttamente, ma che richiederà funzioni per essere chiamato O (n), che non è migliore rispetto all'utilizzo di un elenco, o anche un grande if / then / else è.
Se la vostra collezione di potenziali predicati è troppo lungo per questo di essere un'opzione, è necessario creare la propria struttura dei dati per soddisfare i vostri scopi. Se si sta solo pensando di definire i valori in base a intervalli interi, questo non dovrebbe essere difficile, ma potrebbe sfuggire di mano se il vostro predicati diventano più complessi.
Una nota a parte, il linguaggio F #, che ha un supporto integrato per questo tipo di definizione utilizzando Partita Espressioni . Non so come va sulla compilazione dei rami, ma suppongo che sia abbastanza intelligente su di esso.
Modifica
Ecco un esempio di utilizzo di un espressione Partita in F # per qualcosa di simile:
// Define the "choose" function
let choose value =
match value with
| v when v < 10 -> 1
| v when v > 90 -> 2
| _ -> 0
// Test the "choose" function
let choice1 = choose 5
let choice2 = choose 15
let choice3 = choose 95
Il codice sopra ottengono i seguenti valori:
choice1 = 1
choice2 = 0
choice3 = 2
Non ho mai effettivamente lavorato con F # prima, quindi si dovrà guardarsi intorno per come utilizzare una funzione da F # in un programma C #.
Si dovrà scorrere i criteri e gestito ogni predicato contro l'ingresso per vedere se corrisponde. Non vedo alcuna ragione per usare un dizionario qui.