một cây biểu thức lambda có thể không chứa một toán tử lan truyền rỗng


92

Câu hỏi : Dòng price = co?.price ?? 0,trong đoạn mã sau cho tôi lỗi ở trên. nhưng nếu tôi loại bỏ ?từ co.?nó hoạt động tốt. Tôi đã cố gắng làm theo ví dụ MSDN này mà họ đang sử dụng ?trực tuyến select new { person.FirstName, PetName = subpet?.Name ?? String.Empty };Vì vậy, có vẻ như tôi cần hiểu khi nào thì nên sử dụng ?với ??và khi nào thì không.

Lỗi :

một cây biểu thức lambda có thể không chứa một toán tử lan truyền rỗng

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,
                  ....
                  ....
              };
    ....
    ....
 }

Xin vui lòng gửi lỗi ...
Willem Van Onsem

3
Người đàn ông Tôi ước C # hỗ trợ điều này!
nawfal 14/02/18

Câu trả lời:


143

Ví dụ bạn đang trích dẫn từ sử dụng LINQ thành Đối tượng, trong đó các biểu thức lambda ngầm định trong truy vấn được chuyển đổi thành các đại biểu ... trong khi bạn đang sử dụng EF hoặc tương tự, với các IQueryable<T>truy vấn, trong đó các biểu thức lambda được chuyển đổi thành cây biểu thức . Cây biểu thức không hỗ trợ toán tử điều kiện null (hoặc bộ giá trị).

Chỉ cần làm theo cách cũ:

price = co == null ? 0 : (co.price ?? 0)

(Tôi tin rằng toán tử kết hợp rỗng là tốt trong cây biểu thức.)


Trong trường hợp bạn đang sử dụng Dynamic LINQ (System.Linq.Dynamic.Core), bạn có thể sử dụng np()phương pháp này. Xem github.com/StefH/System.Linq.Dynamic.Core/wiki/NullPropagation
Stef Heyenrath

10

Mã bạn liên kết để sử dụng List<T>. List<T>nông cụ IEnumerable<T>nhưng không IQueryable<T>. Trong trường hợp đó, phép chiếu được thực thi trong bộ nhớ và ?.hoạt động.

Bạn đang sử dụng một số IQueryable<T>, hoạt động rất khác. Đối với IQueryable<T>, một bản trình bày của phép chiếu được tạo và nhà cung cấp LINQ của bạn quyết định phải làm gì với nó trong thời gian chạy. Vì lý do tương thích ngược, ?.không thể sử dụng ở đây.

Tùy thuộc vào nhà cung cấp LINQ của bạn, bạn có thể sử dụng đơn giản .mà vẫn không nhận được bất kỳ NullReferenceException.


@hvd Bạn có thể giải thích tại sao điều này là cần thiết để tương thích ngược không?
jag

1
@jag Tất cả các nhà cung cấp LINQ đã được tạo trước khi giới thiệu ?.sẽ không được chuẩn bị để xử lý ?.theo bất kỳ cách hợp lý nào.

1
Nhưng ?.có phải là một nhà điều hành mới không? Vì vậy, mã cũ sẽ không sử dụng ?.và do đó không bị hỏng. Nhà cung cấp Linq không được chuẩn bị để xử lý nhiều thứ khác như các phương pháp CLR.
jag

2
@jag Đúng, mã cũ kết hợp với các nhà cung cấp LINQ cũ sẽ không bị ảnh hưởng. Mã cũ sẽ không được sử dụng ?.. Mã mới có thể đang sử dụng các nhà cung cấp LINQ cũ, được chuẩn bị để xử lý các phương thức CLR mà chúng không nhận ra (bằng cách đưa ra một ngoại lệ), vì chúng phù hợp tuyệt vời với mô hình đối tượng cây biểu thức hiện có. Hoàn toàn cây biểu hiện mới các loại nút không phù hợp.

3
Căn cứ vào số ngoại lệ ném ra bởi các nhà cung cấp LINQ đã có, hầu như không có vẻ như một trade-off đáng giá - "chúng tôi đã từng không hỗ trợ, vì vậy chúng tôi thà không bao giờ có thể"
NetMage

1

Câu trả lời của Jon Skeet là đúng, trong trường hợp của tôi, tôi đang sử dụng DateTimecho lớp Thực thể của mình. Khi tôi cố gắng sử dụng like

(a.DateProperty == null ? default : a.DateProperty.Date)

Tôi đã có lỗi

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

Vì vậy, tôi cần thay đổi DateTime?cho lớp thực thể của mình và

(a.DateProperty == null ? default : a.DateProperty.Value.Date)

Đây không phải là về toán tử lan truyền null.
Gert Arnold

Tôi thích cách bạn đề cập rằng Jon Skeet đã đúng, cho thấy rằng bằng cách nào đó anh ta có thể sai. Tốt!
Klicker

0

Trong khi cây biểu thức không hỗ trợ việc truyền null trong C # 6.0, những gì chúng ta có thể làm là tạo một khách truy cập sửa đổi cây biểu thức để truyền null an toàn, giống như toán tử làm!

Đây là của tôi:

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);
        }
    }
}

Nó vượt qua các bài kiểm tra sau:

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");
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.