К сожалению, я не вижу, как по-настоящему получить, во время компиляции, Func
и Expression
из того же лямбда. Тем не менее, вы могли бы по крайней мере инкапсулировать разницу, и вы также можете отложить компиляцию Func
до первого использования. Вот решение, которое делает все возможное и может удовлетворить ваши потребности, хотя оно не совсем подходит к тому, что вам действительно нужно (оценка времени компиляции как Expression
, так и Func
).
Пожалуйста, обратите внимание, что это работает отлично без с помощью атрибута [DelegateConstraint]
(от Fody.ExtraConstraints), но с ним, вы получите время компиляции проверки параметра конструктора. Атрибуты заставляют классы действовать так, как будто они имеют ограничение where T : Delegate
, которое в настоящее время не поддерживается на C#, хотя оно -, поддерживаемое в ILE (не уверен, что я говорю это правильно, но вы получите эту идею).
public class VersatileLambda<[DelegateConstraint] T> where T : class {
private readonly Expression<T> _expression;
private readonly Lazy<T> _funcLazy;
public VersatileLambda(Expression<T> expression) {
if (expression == null) {
throw new ArgumentNullException(nameof(expression));
}
_expression = expression;
_funcLazy = new Lazy<T>(expression.Compile);
}
public static implicit operator Expression<T>(VersatileLambda<T> lambda) {
return lambda?._expression;
}
public static implicit operator T(VersatileLambda<T> lambda) {
return lambda?._funcLazy.Value;
}
public Expression<T> AsExpression() { return this; }
public T AsLambda() { return this; }
}
public class WhereConstraint<[DelegateConstraint] T> : VersatileLambda<Func<T, bool>> {
public WhereConstraint(Expression<Func<T, bool>> lambda)
: base(lambda) { }
}
красота неявном является то, что в условиях, когда конкретный Expression<Func<>>
или Func<>
ожидается, вы не должны делать что-либо вообще, просто, использования его.
Теперь, с учетом объекта:
public partial class MyObject {
public int Value { get; set; }
}
Это представлено в базе данных следующим образом:
CREATE TABLE dbo.MyObjects (
Value int NOT NULL CONSTRAINT PK_MyObjects PRIMARY KEY CLUSTERED
);
Тогда это работает так:
var greaterThan5 = new WhereConstraint<MyObject>(o => o.Value > 5);
// Linq to Objects
List<MyObject> list = GetObjectsList();
var filteredList = list.Where(greaterThan5).ToList(); // no special handling
// Linq to Entities
IQueryable<MyObject> myObjects = new MyObjectsContext().MyObjects;
var filteredList2 = myObjects.Where(greaterThan5).ToList(); // no special handling
Если неявное преобразование ISN 't подходит, вы можете явно указывать на целевой тип:
var expression = (Expression<Func<MyObject, bool>>) greaterThan5;
Обратите внимание, что вы не действительно нужен WhereConstraint
класс, или вы могли бы избавиться от VersatileLambda
, перемещая его содержимое в WhereConstraint
, но мне нравилось делать два отдельных (как теперь вы можете использовать VersatileLambda
что-то который возвращается, кроме bool
). (. И эта разница в основном то, что отличает мой ответ от Диего) Используя VersatileLambda
, как теперь выглядит следующим образом (вы можете увидеть, почему я завернул):
var vl = new VersatileLambda<Func<MyObject, bool>>(o => o.Value > 5);
Я подтвердил, что это прекрасно работает для IEnumerable
а также IQueryable
, правильно проецируя лямбда-выражение в SQL, что подтверждается запуском SQL Profiler.
Кроме того, вы можете делать действительно классные вещи с помощью выражений, которые нельзя сделать с помощью лямбда. Проверьте это:
public static class ExpressionHelper {
public static Expression<Func<TFrom, TTo>> Chain<TFrom, TMiddle, TTo>(
this Expression<Func<TFrom, TMiddle>> first,
Expression<Func<TMiddle, TTo>> second
) {
return Expression.Lambda<Func<TFrom, TTo>>(
new SwapVisitor(second.Parameters[0], first.Body).Visit(second.Body),
first.Parameters
);
}
// this method thanks to Marc Gravell
private class SwapVisitor : ExpressionVisitor {
private readonly Expression _from;
private readonly Expression _to;
public SwapVisitor(Expression from, Expression to) {
_from = from;
_to = to;
}
public override Expression Visit(Expression node) {
return node == _from ? _to : base.Visit(node);
}
}
}
var valueSelector = new Expression<Func<MyTable, int>>(o => o.Value);
var intSelector = new Expression<Func<int, bool>>(x => x > 5);
var selector = valueSelector.Chain<MyTable, int, bool>(intSelector);
Вы можете создать перегрузку Chain
, которая принимает VersatileLambda
в качестве первого параметра, и возвращает VersatileLambda
. Теперь ты действительно испепеляешься.
Ваша «VersatileLambda» - это именно то, что я изначально предполагал как решение, прежде чем реализовать C# не поддерживает «делегат» как ограничение типа. Я столкнулся с обходным решением Fody, и я прочитал то же самое, что вы сказали, - что это ограничение компилятора, а не ограничение .NET. Я бы выбрал решение Диего по этому поводу, только потому, что это вводит зависимость от Fody, но если делегат был изначально поддерживается как ограничение типа, то это решение лучше всего потому, что для него требуется только один класс, который будет поддерживать любую лямбду. Я хочу, чтобы ограничения типа «Делегат» и «Числовые» поддерживались. – Triynko
На второй, хотя, я собираюсь отметить это как ответ, потому что он будет работать даже без Fody. Сам класс 'Expression' требует типа делегата, но для определения имени типа типа' TDelegate'; поэтому, если он достаточно хорош для рамки, для меня это достаточно хорошо. В худшем случае, если используется тип, не являющийся делегатом, он будет выходить из строя в 'Expression' 'окончательный листинг в вызове' TDelegate Compile': '(TDelegate) ((object) LambdaCompiler.Compile (this, null)) ; ' –
Triynko
Для чего это стоит, Диего и мой код очень похожи, и он сначала публиковал, но это было почти то, о чем я думал. Я также пошел на некоторые существенные проблемы, чтобы проверить, что неявное преобразование работает очень хорошо в выражении linq для сущностей. Я думаю, что метод AsFunc или ToFunc стоит того. (И выражение.) Я также думаю, что цепочка, которую я представил, добавлена. И мне нравится имя, которое я выбрал лучше. :) – ErikE