より良い設計は、ルールがテスト自体(または任意の値)を適用することです。
Funcインスタンスでこれを行うことにより、次のように最も柔軟性が得られます。
IEnumerable<Func<T,bool> tests; // defined somehow at runtime
foreach (var item in items)
{
foreach (var test in tests)
{
if (test(item))
{
//do work with item
}
}
}
次に、コンパイル時の強い型チェックのための特定のテストは次のようになります。
public Func<T,bool> FooEqualsX<T,V>(V x)
{
return t => EqualityComparer<V>.Default.Equals(t.Foo, x);
}
反射型の場合
public Func<T,bool> MakeTest<T,V>(string name, string op, V value)
{
Func<T,V> getter;
var f = typeof(T).GetField(name);
if (f != null)
{
if (!typeof(V).IsAssignableFrom(f.FieldType))
throw new ArgumentException(name +" incompatible with "+ typeof(V));
getter= x => (V)f.GetValue(x);
}
else
{
var p = typeof(T).GetProperty(name);
if (p == null)
throw new ArgumentException("No "+ name +" on "+ typeof(T));
if (!typeof(V).IsAssignableFrom(p.PropertyType))
throw new ArgumentException(name +" incompatible with "+ typeof(V));
getter= x => (V)p.GetValue(x, null);
}
switch (op)
{
case "==":
return t => EqualityComparer<V>.Default.Equals(getter(t), value);
case "!=":
return t => !EqualityComparer<V>.Default.Equals(getter(t), value);
case ">":
return t => Comparer<V>.Default.Compare(getter(t), value) > 0;
// fill in the banks as you need to
default:
throw new ArgumentException("unrecognised operator '"+ op +"'");
}
}
本当に内省的で、コンパイル時に知らなくてもリテラルを処理したい場合は、CSharpCodeProviderを使用して、次のような関数を想定してコンパイルできます。
public static bool Check(T t)
{
// your code inserted here
}
もちろん、これは大規模なセキュリティホールであるため、このためのコードを提供できる人は誰でも完全に信頼されている必要があります。これは、特定のニーズに合わせていくらか制限された実装です(健全性チェックはまったくありません)
private Func<T,bool> Make<T>(string name, string op, string value)
{
var foo = new Microsoft.CSharp.CSharpCodeProvider()
.CompileAssemblyFromSource(
new CompilerParameters(),
new[] { "public class Foo { public static bool Eval("+
typeof(T).FullName +" t) { return t."+
name +" "+ op +" "+ value
+"; } }" }).CompiledAssembly.GetType("Foo");
return t => (bool)foo.InvokeMember("Eval",
BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod ,
null, null, new object[] { t });
}
// use like so:
var f = Make<string>("Length", ">", "2");
これを任意の型で機能させるには、コンパイラパラメータで参照する型のターゲットアセンブリを見つけるために、もう少し反映する必要があります。
private bool Eval(object item, string name, string op, string value)
{
var foo = new Microsoft.CSharp.CSharpCodeProvider()
.CompileAssemblyFromSource(
new CompilerParameters(),
new[] { "public class Foo { public static bool Eval("+
item.GetType().FullName +" t) "+
"{ return t."+ name +" "+ op +" "+ value +"; } }"
}).CompiledAssembly.GetType("Foo");
return (bool)foo.InvokeMember("Eval",
BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod ,
null, null, new object[] { item });
}
上記のコードはすべて概念実証であり、健全性チェックがなく、パフォーマンスに深刻な問題があります。
さらに洗練されたものにしたい場合は、DynamicMethodインスタンスでReflection.Emitを使用して(デフォルトの比較インスタンスではなく適切な演算子を使用して)実行できますが、これには、演算子がオーバーライドされた型の複雑な処理が必要になります。
チェックコードを非常に一般的なものにすることで、必要に応じて将来さらに多くのテストを含めることができます。基本的に、関数のみを気にするコードの部分を、これらの関数を提供するコードからt-> true/falseから分離します。