表达式树lambda可能不包含空传播运算符

nam*_*nam 66 c# linq null-coalescing-operator

问题:price = co?.price ?? 0,以下代码中的行给出了上述错误.但如果我?co.?它删除它工作正常.我试图按照此MSDN例如,他们使用的是?上线select new { person.FirstName, PetName = subpet?.Name ?? String.Empty };所以,看来我需要了解什么时候使用???和何时不.

错误:

表达式树lambda可能不包含空传播运算符

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,
                  ....
                  ....
              };
    ....
    ....
 }
Run Code Online (Sandbox Code Playgroud)

Jon*_*eet 99

您引用的示例使用LINQ to Objects,其中查询中的隐式lambda表达式转换为委托 ...而您使用EF或类似的IQueryable<T>查询,其中lambda表达式转换为表达式树.表达式树不支持空条件运算符(或元组).

只是按照旧方式去做:

price = co == null ? 0 : (co.price ?? 0)
Run Code Online (Sandbox Code Playgroud)

(我相信null-coalescing运算符在表达式树中很好.)

  • 如果您使用动态 LINQ (System.Linq.Dynamic.Core),则可以使用“np()”方法。请参阅 https://github.com/StefH/System.Linq.Dynamic.Core/wiki/NullPropagation (2认同)
  • @Calculuswhiz:无论如何,它都是一个 lambda 表达式。但并非所有 lambda 表达式实际上都支持转换为表达式树。我相信在团队决定采用空条件之前,空传播是一个较早的术语。 (2认同)

小智 8

Jon Skeet 的答案是正确的,就我而言,我用于DateTime我的实体类。当我尝试使用类似

(a.DateProperty == null ? default : a.DateProperty.Date)
Run Code Online (Sandbox Code Playgroud)

我有错误

Property 'System.DateTime Date' is not defined for type 'System.Nullable`1[System.DateTime]' (Parameter 'property')
Run Code Online (Sandbox Code Playgroud)

所以我需要改变DateTime?我的实体类

(a.DateProperty == null ? default : a.DateProperty.Value.Date)
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢你提到乔恩·斯基特是对的,暗示他有可能是错的。好一个! (12认同)

小智 6

您链接的代码使用List<T>.List<T>实现IEnumerable<T>但不是IQueryable<T>.在这种情况下,投影在存储器中执行并?.起作用.

你使用的是一些IQueryable<T>非常不同的东西.因为IQueryable<T>,创建了投影的表示,并且您的LINQ提供程序决定在运行时如何处理它.出于向后兼容性原因,?.此处不能使用.

根据您的LINQ提供程序,您可以使用plain .,但仍然无法获取NullReferenceException.

  • 考虑到 LINQ 提供者已经抛出的异常数量,似乎很难进行权衡——“我们过去不支持,所以我们宁愿永远不支持” (3认同)
  • 但是“?.”是一个新的运算符不是吗?所以旧的代码不会使用`?.`,因此不会被破坏。Linq 提供程序不准备处理许多其他事情,例如 CLR 方法。 (2认同)
  • @jag 是的,旧代码与旧 LINQ 提供程序相结合不会受到影响。旧代码不会使用`?.`。新代码可能正在使用旧的 LINQ 提供程序,这些提供程序 *准备* 处理它们无法识别的 CLR 方法(通过抛出异常),因为它们非常适合现有的表达式树对象模型。全新的表达式树节点类型不适合。 (2认同)

lea*_*moh 5

虽然表达式树不支持 C# 6.0 null 传播,但我们可以做的是创建一个访问者来修改表达式树以实现安全 null 传播,就像运算符一样!

这是我的:

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

它通过了以下测试:

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");
    }
}
Run Code Online (Sandbox Code Playgroud)