表达式树 lambda 可能不包含空传播运算符
Posted
技术标签:
【中文标题】表达式树 lambda 可能不包含空传播运算符【英文标题】:An expression tree lambda may not contain a null propagating operator 【发布时间】:2017-11-24 16:02:34 【问题描述】:以下代码中的price = co?.price ?? 0,
行给出了上述错误,但如果我从co.?
中删除?
,它就可以正常工作。
我试图关注this MSDN example,他们在select new person.FirstName, PetName = subpet?.Name ?? String.Empty ;
线上使用?
,所以,我似乎需要了解何时将?
与??
一起使用,何时不使用。
错误:
public class CustomerOrdersModelView
public string CustomerID get; set;
public int FY get; set;
public float? price get; set;
....
....
public async Task<IActionResult> ProductAnnualReport(string rpt)
var qry = from c in _context.Customers
join ord in _context.Orders
on c.CustomerID equals ord.CustomerID into co
from m in co.DefaultIfEmpty()
select new CustomerOrdersModelView
CustomerID = c.CustomerID,
FY = c.FY,
price = co?.price ?? 0,
....
....
;
....
....
【问题讨论】:
我希望 C# 支持这个! 【参考方案1】:您引用的示例使用 LINQ to Objects,其中查询中的隐式 lambda 表达式被转换为 delegates... 而您使用的是 EF 或类似的 IQueryable<T>
queryies ,其中 lambda 表达式被转换为 表达式树。表达式树不支持空条件运算符(或元组)。
照旧做:
price = co == null ? 0 : (co.price ?? 0)
(我相信空合并运算符在表达式树中很好。)
【讨论】:
如果您使用的是动态 LINQ (System.Linq.Dynamic.Core),您可以使用np()
方法。见github.com/StefH/System.Linq.Dynamic.Core/wiki/NullPropagation
@Jon LINQ 是否能够将其转换为 SQL,还是会导致过滤发生在内存中?
@DaniMazahreh:正如我之前所说,表达式树不支持空条件运算符 - 所以我希望你会得到一个编译错误。
哦,我明白了。从编码方面来看,无论哪种方式,它看起来都像是一个 lambda 表达式,但只有 delegate/Func
支持运算符的使用。另外,我想知道错误是否有原因显示“null-propagating”而不是“null-conditional”,这是官方文档所说的?
@Calculuswhiz:无论哪种方式,它是一个 lambda 表达式。但并非所有 lambda 表达式实际上都支持转换为表达式树。我相信空传播是团队确定空条件之前的较早术语。【参考方案2】:
您链接到的代码使用List<T>
。 List<T>
实现 IEnumerable<T>
但不是 IQueryable<T>
。在这种情况下,投影在内存中执行,?.
起作用。
您正在使用一些IQueryable<T>
,它的工作方式非常不同。对于IQueryable<T>
,会创建投影的表示,并且您的 LINQ 提供者决定在运行时如何处理它。出于向后兼容的原因,此处不能使用?.
。
根据您的 LINQ 提供商,您可能可以使用普通的 .
,但仍然无法获得任何 NullReferenceException
。
【讨论】:
@hvd 你能解释一下为什么这是向后兼容所必需的吗? @jag 在引入?.
之前已经创建的所有LINQ 提供程序都不会准备以任何合理的方式处理?.
。
但是?.
是一个新的运营商,不是吗?所以旧代码不会使用?.
,因此不会被破坏。 Linq 提供程序不准备处理许多其他事情,例如 CLR 方法。
@jag 对,旧代码与旧 LINQ 提供程序结合使用不会受到影响。旧代码不会使用?.
。新代码可能正在使用旧的 LINQ 提供程序,这些提供程序准备处理它们无法识别的 CLR 方法(通过引发异常),因为这些方法非常适合现有的表达式树对象模型。全新的表达式树节点类型不适合。
考虑到 LINQ 提供程序已经抛出的异常数量,似乎不值得权衡 - “我们过去不支持,所以我们宁愿永远不支持”【参考方案3】:
虽然表达式树不支持 C# 6.0 的空值传播,但我们可以创建一个访问者来修改表达式树以实现安全的空值传播,就像操作符一样!
Here is mine:
public class NullPropagationVisitor : ExpressionVisitor
private readonly bool _recursive;
public NullPropagationVisitor(bool recursive)
_recursive = recursive;
protected override Expression VisitUnary(UnaryExpression propertyAccess)
if (propertyAccess.Operand is MemberExpression mem)
return VisitMember(mem);
if (propertyAccess.Operand is MethodCallExpression met)
return VisitMethodCall(met);
if (propertyAccess.Operand is ConditionalExpression cond)
return Expression.Condition(
test: cond.Test,
ifTrue: MakeNullable(Visit(cond.IfTrue)),
ifFalse: MakeNullable(Visit(cond.IfFalse)));
return base.VisitUnary(propertyAccess);
protected override Expression VisitMember(MemberExpression propertyAccess)
return Common(propertyAccess.Expression, propertyAccess);
protected override Expression VisitMethodCall(MethodCallExpression propertyAccess)
if (propertyAccess.Object == null)
return base.VisitMethodCall(propertyAccess);
return Common(propertyAccess.Object, propertyAccess);
private BlockExpression Common(Expression instance, Expression propertyAccess)
var safe = _recursive ? base.Visit(instance) : instance;
var caller = Expression.Variable(safe.Type, "caller");
var assign = Expression.Assign(caller, safe);
var acess = MakeNullable(new ExpressionReplacer(instance,
IsNullableStruct(instance) ? caller : RemoveNullable(caller)).Visit(propertyAccess));
var ternary = Expression.Condition(
test: Expression.Equal(caller, Expression.Constant(null)),
ifTrue: Expression.Constant(null, acess.Type),
ifFalse: acess);
return Expression.Block(
type: acess.Type,
variables: new[]
caller,
,
expressions: new Expression[]
assign,
ternary,
);
private static Expression MakeNullable(Expression ex)
if (IsNullable(ex))
return ex;
return Expression.Convert(ex, typeof(Nullable<>).MakeGenericType(ex.Type));
private static bool IsNullable(Expression ex)
return !ex.Type.IsValueType || (Nullable.GetUnderlyingType(ex.Type) != null);
private static bool IsNullableStruct(Expression ex)
return ex.Type.IsValueType && (Nullable.GetUnderlyingType(ex.Type) != null);
private static Expression RemoveNullable(Expression ex)
if (IsNullableStruct(ex))
return Expression.Convert(ex, ex.Type.GenericTypeArguments[0]);
return ex;
private class ExpressionReplacer : ExpressionVisitor
private readonly Expression _oldEx;
private readonly Expression _newEx;
internal ExpressionReplacer(Expression oldEx, Expression newEx)
_oldEx = oldEx;
_newEx = newEx;
public override Expression Visit(Expression node)
if (node == _oldEx)
return _newEx;
return base.Visit(node);
它通过了以下测试:
private static string Foo(string s) => s;
static void Main(string[] _)
var visitor = new NullPropagationVisitor(recursive: true);
Test1();
Test2();
Test3();
void Test1()
Expression<Func<string, char?>> f = s => s == "foo" ? 'X' : Foo(s).Length.ToString()[0];
var fBody = (Expression<Func<string, char?>>)visitor.Visit(f);
var fFunc = fBody.Compile();
Debug.Assert(fFunc(null) == null);
Debug.Assert(fFunc("bar") == '3');
Debug.Assert(fFunc("foo") == 'X');
void Test2()
Expression<Func<string, int>> y = s => s.Length;
var yBody = visitor.Visit(y.Body);
var yFunc = Expression.Lambda<Func<string, int?>>(
body: yBody,
parameters: y.Parameters)
.Compile();
Debug.Assert(yFunc(null) == null);
Debug.Assert(yFunc("bar") == 3);
void Test3()
Expression<Func<char?, string>> y = s => s.Value.ToString()[0].ToString();
var yBody = visitor.Visit(y.Body);
var yFunc = Expression.Lambda<Func<char?, string>>(
body: yBody,
parameters: y.Parameters)
.Compile();
Debug.Assert(yFunc(null) == null);
Debug.Assert(yFunc('A') == "A");
【讨论】:
【参考方案4】:Jon Skeet 的回答是正确的,在我的情况下,我使用 DateTime
作为我的 Entity 类。
当我尝试使用like时
(a.DateProperty == null ? default : a.DateProperty.Date)
我有错误
Property 'System.DateTime Date' is not defined for type 'System.Nullable`1[System.DateTime]' (Parameter 'property')
所以我需要为我的实体类更改DateTime?
和
(a.DateProperty == null ? default : a.DateProperty.Value.Date)
【讨论】:
这与空传播运算符无关。 我喜欢你提到 Jon Skeet 是对的,暗示他有可能是错的。不错!以上是关于表达式树 lambda 可能不包含空传播运算符的主要内容,如果未能解决你的问题,请参考以下文章