Лучший дизайн для вас будет для вашего правила, чтобы применить сам тест (или произвольное значение)
Делая это с экземплярами 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 из кода, который предоставляет эти функции.
Тогда решение с делегатами, похоже, очень близко к тому, что вам нужно; если stringCode не является действительно динамичным (контент, предоставляемый пользователем, «на лету»). Затем вам понадобится что-то через DLR (который будет готов в .NET 4.0, и я не знаю достаточно о том, чтобы помочь) –
, пока ваш конкретный случай остается постоянным, и вы можете его достичь. Если вам нужна возможность делать * произвольную * логику, а фиксированную форму, описанную, тогда у вас возникнут проблемы. По сути, если набор всех входов в ваш «код» известен заранее, и никаких побочных эффектов нет, кроме возврата одного значения (опять же, когда тип известен во время компиляции (даже если это только «объект») то это возможно. Все, что находится за пределами этого, и вы находитесь за пределами того, что может сделать что-то вроде «eval». – ShuggyCoUk
А я просто видел ваше обновление. Нет, вы не можете получить полные возможности того, что eval делает в чем-то вроде javascript. стремление получить все больше и больше функциональности станет экспоненциально сложнее, так как вам нужно больше и больше аспектов, которые бы сработали, если бы вы могли просто вставить код в класс во время компиляции. В конце концов вы бы нанесли что-то вроде лямбда, где локальная переменная был повышен до переменной экземпляра, и интроспективное состояние выполнения больше не было похоже на лексическую структуру времени компиляции, чтобы позволить этой концепции работать дальше. – ShuggyCoUk