Лучшим дизайном для вас было бы правило применять сам тест (или к произвольному значению)
Делая это с экземплярами 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 });
}
Весь приведенный выше код является просто подтверждением концепции, в нем отсутствует проверка работоспособности и имеются серьезные проблемы с производительностью.
Если вы хотите быть еще более искушенным, вы можете использовать Reflection.Emit с экземплярами DynamicMethod, чтобы сделать это (используя правильные операторы, а не экземпляры компаратора по умолчанию), но это потребует сложной обработки для типов с переопределенными операторами.
Делая ваш код проверки очень универсальным, вы можете включать в будущем больше тестов, сколько вам нужно. По сути, изолируйте часть вашего кода, которая заботится только о функции, из t -> true / false из кода, который предоставляет эти функции.