Se l'elenco di stringhe è noto in anticipo, è possibile utilizzare IL Emit per creare un albero di diramazione basato sui caratteri nella stringa di ricerca e risolvere un indice in un array. Questo dovrebbe darti una velocità di ricerca piuttosto veloce.
Ho implementato qualcosa di simile per divertimento e pratica mentre stavo imparando IL Emit. Funziona in base ai casi di test limitati che ho provato, ma vorrete sicuramente renderlo più robusto e creare test unitari adeguati per il codice di produzione. Ho pubblicato il codice raw (è un po 'lungo); avresti bisogno di cambiare alcune cose per il tuo caso particolare, ma la logica di base è lì. Non ho incluso la funzione helper EmitLdc
(ci sono molti sovraccarichi), ma è solo una funzione per caricare una costante arbitraria nello stack. È possibile semplicemente sostituire le chiamate per emettere la stringa e i tipi numerici direttamente utilizzando rispettivamente Ldstr e Ldc_I4.
protected void GenerateNestedStringSearch<T>(ILGenerator gen, T[] values, Func<T, string> getName, Action<ILGenerator, T> loadValue)
{
//We'll jump here if no match found
Label notFound = gen.DefineLabel();
//Try to match the string
GenerateNestedStringSearch(gen, notFound, values, getName, loadValue, 0);
//Nothing found, so don't need string anymore
gen.MarkLabel(notFound);
gen.Emit(OpCodes.Pop);
//Throw ArgumentOutOfRangeException to indicate not found
gen.EmitLdc("name");
gen.EmitLdc("Binding does not contain a tag with the specified name: ");
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Call, typeof(String).GetMethod("Concat",
BindingFlags.Static | BindingFlags.Public,
null,
new[] { typeof(string), typeof(string) },
null));
gen.Emit(OpCodes.Newobj,
typeof(ArgumentOutOfRangeException).GetConstructor(new[] { typeof(string), typeof(string) }));
gen.Emit(OpCodes.Throw);
}
protected void GenerateNestedStringSearch<T>(ILGenerator gen, Label notFound, T[] values, Func<T, string> getName, Action<ILGenerator, T> loadValue, int charIndex)
{
//Load the character from the candidate string for comparison
gen.Emit(OpCodes.Dup);
gen.EmitLdc(charIndex);
gen.Emit(OpCodes.Ldelem_U2);
//Group possible strings by their character at this index
//We ignore strings that are too short
var strings = values.Select(getName).ToArray();
var stringsByChar =
from x in strings
where charIndex < x.Length
group x by x[charIndex]
into g
select new { FirstChar = g.Key, Strings = g };
foreach (var grouped in stringsByChar)
{
//Compare source character to group character and jump ahead if it doesn't match
Label charNotMatch = gen.DefineLabel();
gen.Emit(OpCodes.Dup);
gen.EmitLdc(grouped.FirstChar);
gen.Emit(OpCodes.Bne_Un, charNotMatch);
//If there is only one string in this group, we've found our match
int count = grouped.Strings.Count();
Debug.Assert(count > 0);
if (count == 1)
{
//Don't need the source character or string anymore
gen.Emit(OpCodes.Pop);
gen.Emit(OpCodes.Pop);
//Return the value for this name
int index = Array.FindIndex(strings, s => s == grouped.Strings.First());
loadValue(gen, values[index]);
gen.Emit(OpCodes.Ret);
}
else
{
//Don't need character anymore
gen.Emit(OpCodes.Pop);
//If there is a string that ends at this character
string endString = grouped.Strings.FirstOrDefault(s => s.Length == (charIndex + 1));
if (endString != null)
{
//Get string length
gen.Emit(OpCodes.Dup);
gen.Emit(OpCodes.Call, typeof(char[]).GetProperty("Length").GetGetMethod());
//If string length matches ending string
gen.EmitLdc(endString.Length);
Label keepSearching = gen.DefineLabel();
gen.Emit(OpCodes.Bne_Un, keepSearching);
//Don't need the source string anymore
gen.Emit(OpCodes.Pop);
//Create an UnboundTag for this index
int index = Array.FindIndex(strings, s => s == endString);
loadValue(gen, values[index]);
gen.Emit(OpCodes.Ret);
//String length didn't match
gen.MarkLabel(keepSearching);
}
//Need to consider strings starting with next character
var nextValues = from s in grouped.Strings
join v in values on s equals getName(v)
select v;
GenerateNestedStringSearch(gen, notFound, nextValues.ToArray(),
getName, loadValue, charIndex + 1);
}
//This character didn't match, so consider next character
gen.MarkLabel(charNotMatch);
}
//We don't need the character anymore
gen.Emit(OpCodes.Pop);
//No string match, so jump to Not Found at end of check
gen.Emit(OpCodes.Br, notFound);
}
EDIT: Ho appena realizzato che non si è in realtà utilizza chiavi stringa, quindi questo potrebbe non essere applicabile al vostro caso. È possibile utilizzare una tecnica simile con altre ricerche purché si abbia la possibilità di raccogliere tutte le chiavi richieste insieme prima di usarle. Terrò questo qui nel caso in cui qualcun altro potrebbe trovare utile.
Sembra che tu abbia molte costanti di stringa, vuol dire che hai un elenco dei tasti del dizionario prima del tempo? Se è così, basta usare una classe di vaniglia per evitare l'overhead della tua funzione di hash. – Juliet
Sì, in realtà ho usato solo le costanti di stringa per semplificare lo pseudo codice. Nell'esempio "attuale" del mondo reale sto usando chiavi più veloci. – CodingInsomnia
[Ecco un semplice confronto] (http://spiritofdev.blogspot.in/2011/12/performance-of-c-40-dynamic-vs.html). Se è possibile ignorare il costo di costruzione, le ricerche dovrebbero essere eseguite in modo simile. – nawfal