如果預先知道字符串列表,則可以使用IL Emit根據搜索字符串中的字符創建分支樹並將其解析爲數組中的索引。這應該會給你非常快的查詢速度。
我在學習IL Emit的同時,爲了好玩和練習,我實現了這樣的東西。它基於我嘗試的有限測試案例工作,但您一定希望使其更強大,併爲生產代碼創建適當的單元測試。我已經發布了原始代碼(這有點長);你需要爲你的特定情況改變一些東西,但核心邏輯在那裏。我沒有包含EmitLdc
輔助函數(有很多重載),但它只是一個函數,可以將任意常量加載到堆棧。您可以直接使用Ldstr和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);
}
編輯:我只是意識到,你實際上並沒有使用字符串鍵,所以這可能並不適用於你的情況。只要您能夠在使用它們之前收集所有必需的密鑰,就可以使用類似的技術與其他查找功能。我會保留這裏,以防其他人可能會覺得它有用。
它看起來像你有很多字符串常量,這是否意味着你有一個你的字典鍵提前列表?如果是這樣,只需使用一個vanilla類來避免散列函數的開銷。 – Juliet 2010-08-19 14:17:49
是的,我實際上只使用字符串常量來簡化僞代碼。在「當前」現實世界的例子中,我使用的是更快的鍵。 – CodingInsomnia 2010-08-19 14:25:02
[這是一個簡單的比較](http://spiritofdev.blogspot.in/2011/12/performance-of-c-40-dynamic-vs.html)。如果您可以忽略構建成本,則查找應該以相似的方式執行。 – nawfal 2014-07-17 22:49:01