bir ifade ağacı lambda boş yayılma operatörü içeremez


96

Soru : price = co?.price ?? 0,Aşağıdaki koddaki satır bana yukarıdaki hatayı veriyor. Ben kaldırırsanız ancak ?gelen co.?o cezayı çalışır. Takip etmeye çalışıyordum bu MSDN örnek kullandıkları ?hattı üzerinde select new { person.FirstName, PetName = subpet?.Name ?? String.Empty };Yani, ben ne zaman kullanılacağını anlamak gerekmez görünüyor ?ile ??ve ne zaman değil.

Hata :

bir ifade ağacı lambda boş yayılma operatörü içeremez

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

Lütfen hatayı gönderin ...
Willem Van Onsem

3
Adamım C # bunu desteklemesini diledim!
nawfal

Yanıtlar:


150

Sorgudaki örtük lambda ifadelerinin temsilcilere dönüştürüldüğü LINQ to Objects'ten alıntı yaptığınız örnek IQueryable<T>, lambda ifadelerinin ifade ağaçlarına dönüştürüldüğü sorgulamalarda EF veya benzerini kullanıyorsunuz . İfade ağaçları boş koşullu işleci (veya tuplelar) desteklemez.

Sadece eski yöntemle yap:

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

(Boş birleştirme operatörünün bir ifade ağacında iyi olduğuna inanıyorum.)


Dynamic LINQ (System.Linq.Dynamic.Core) kullanıyorsanız, np()yöntemi kullanabilirsiniz . Bkz. Github.com/StefH/System.Linq.Dynamic.Core/wiki/NullPropagation
Stef Heyenrath

11

Bağlandığınız kod kullanır List<T>. List<T>uygular IEnumerable<T>ama uygulamaz IQueryable<T>. Bu durumda, projeksiyon bellekte yürütülür ve ?.çalışır.

IQueryable<T>Çok farklı çalışan bazılarını kullanıyorsunuz . Çünkü IQueryable<T>, projeksiyonun bir temsili oluşturulur ve LINQ sağlayıcınız çalışma zamanında bununla ne yapılacağına karar verir. Geriye dönük uyumluluk nedeniyle ?.burada kullanılamaz.

LINQ sağlayıcınıza bağlı olarak, düz kullanabilir .ve yine de elde edemeyebilirsiniz NullReferenceException.


@hvd Geriye dönük uyumluluk için bunun neden gerekli olduğunu açıklayabilir misiniz?
jag

1
@jag kullanılmaya başlanmadan önce zaten oluşturulmuş olan tüm LINQ sağlayıcıları, herhangi bir makul şekilde ?.işlemeye hazır olmayacaktı ?..

1
Ama ?.yeni bir operatör hayır mı? Bu yüzden eski kod kullanılmaz ?.ve bu nedenle kırılmaz. Linq Sağlayıcıları, CLR yöntemleri gibi pek çok şeyi işlemeye hazır değildir.
jag

2
@jag Doğru, eski LINQ sağlayıcıları ile birlikte eski kod etkilenmeyecektir. Eski kod kullanılmayacak ?.. Yeni kod, tanımadıkları CLR yöntemlerini (bir istisna atarak) işlemek için hazırlanmış eski LINQ sağlayıcılarını kullanıyor olabilir , çünkü bunlar mevcut ifade ağacı nesne modeline güzel bir şekilde uyar. Tamamen yeni ifade ağacı düğüm türleri uymuyor.

3
LINQ sağlayıcıları tarafından zaten atılan istisnaların sayısı göz önüne alındığında, pek değerli bir değiş tokuş gibi görünmüyor - "
eskiden

1

Jon Skeet'in cevabı doğruydu, benim durumumda DateTimeVarlık sınıfım için kullanıyordum . Beğenmeyi denediğimde

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

Hata yaptım

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

Bu yüzden DateTime?varlık sınıfım için değiştirmem gerekiyordu ve

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

Bu, boş yayılma operatörü ile ilgili değil.
Gert Arnold

Jon Skeet'in haklı olduğunu söyleme şeklinizi seviyorum, onun bir şekilde yanılmış olmasının mümkün olduğunu öne sürüyorsunuz. İyi bir!
Klicker

0

İfade ağacı C # 6.0 boş yayılmasını desteklemese de, yapabileceğimiz şey, operatörün yaptığı gibi, güvenli boş yayılım için ifade ağacını değiştiren bir ziyaretçi oluşturmaktır!

İşte benim:

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

Aşağıdaki testlerden geçer:

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");
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.