文字列のリストが事前にわかっている場合は、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);
}
編集:実際には文字列キーを使用していないことに気付いたので、これはあなたのケースには当てはまらないかもしれません. それらを使用する前に、必要なすべてのキーをまとめて収集できる限り、他のルックアップで同様の手法を使用できます。他の誰かが役に立つかもしれない場合に備えて、これをここに保持します。