132 votes

Un lambda d'arbre d'expression ne peut pas contenir un opérateur de propagation de null

La ligne price = co?.price ?? 0, dans le code suivant me donne l'erreur ci-dessus, mais si je supprime ? de co.?, ça fonctionne bien.

J'essayais de suivre cet exemple de MSDN où ils utilisent ? à la ligne select new { person.FirstName, PetName = subpet?.Name ?? String.Empty };. Donc, il semble que je dois comprendre quand utiliser ? avec ?? et quand ne pas le faire.

Erreur:

une lambda d'arbre d'expression ne peut pas contenir un opérateur de propagation null

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

0 votes

Veuillez poster l'erreur...

7 votes

Homme, j'aurais souhaité que C# prenne en charge cela!

204voto

Jon Skeet Points 692016

L'exemple que vous citiez utilise LINQ sur les objets, où les expressions lambda implicites dans la requête sont converties en délégués... tandis que vous utilisez EF ou similaire, avec des requêtes IQueryable, où les expressions lambda sont converties en arbres d'expression. Les arbres d'expression ne prennent pas en charge l'opérateur conditionnel null (ou les tuples).

Fais-le à l'ancienne :

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

(Je crois que l'opérateur de coalescence de nul est acceptable dans un arbre d'expression.)

0 votes

Au cas où vous utilisez Dynamic LINQ (System.Linq.Dynamic.Core), vous pouvez utiliser la méthode np(). Voir github.com/StefH/System.Linq.Dynamic.Core/wiki/NullPropagati‌​on

0 votes

@Jon Est-ce que LINQ est capable de traduire cela en SQL ou est-ce que cela provoquerait un filtrage en mémoire?

0 votes

@DaniMazahreh : Comme je l'ai dit précédemment, les arbres d'expression ne prennent pas en charge l'opérateur conditionnel nul - donc je m'attendrais à ce que vous obteniez une erreur de compilation.

14voto

Le code auquel vous liez utilise List. List implémente IEnumerable mais pas IQueryable. Dans ce cas, la projection est exécutée en mémoire et ?. fonctionne.

Vous utilisez IQueryable, qui fonctionne très différemment. Pour IQueryable, une représentation de la projection est créée, et votre fournisseur LINQ décide de ce qu'il faut en faire à l'exécution. Pour des raisons de compatibilité ascendante, ?. ne peut pas être utilisé ici.

En fonction de votre fournisseur LINQ, vous pouvez peut-être utiliser simplement . et ne pas obtenir de NullReferenceException.

0 votes

@hvd Pourriez-vous expliquer pourquoi cela est nécessaire pour la compatibilité descendante?

1 votes

@jag Tous les fournisseurs LINQ qui avaient déjà été créés avant l'introduction de ?. n'auraient pas été préparés à gérer ?. de manière raisonnable.

1 votes

Mais le ?. est un nouvel opérateur non? Donc, le code ancien n'utiliserait pas le ?. et ne serait donc pas cassé. Les fournisseurs Linq ne sont pas préparés à gérer de nombreuses autres choses telles que les méthodes CLR.

3voto

leandromoh Points 99

Alors que l'arbre d'expression ne prend pas en charge la propagation nulle de C# 6.0, ce que nous pouvons faire est de créer un visiteur qui modifie l'arbre d'expression pour une propagation nulle sûre, tout comme le fait l'opérateur !

Voici le mien :

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

Il passe les tests suivants :

private static string Foo(string s) => s;

static void Main(string[] _)
{
    var visitor = new NullPropagationVisitor(recursive: true);

    Test1();
    Test2();
    Test3();

    void Test1()
    {
        Expression> f = s => s == "foo" ? 'X' : Foo(s).Length.ToString()[0];

        var fBody = (Expression>)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> y = s => s.Length;

        var yBody = visitor.Visit(y.Body);
        var yFunc = Expression.Lambda>(
                                    body: yBody,
                                    parameters: y.Parameters)
                            .Compile();

        Debug.Assert(yFunc(null) == null);
        Debug.Assert(yFunc("bar") == 3);
    }

    void Test3()
    {
        Expression> y = s => s.Value.ToString()[0].ToString();

        var yBody = visitor.Visit(y.Body);
        var yFunc = Expression.Lambda>(
                                    body: yBody,
                                    parameters: y.Parameters)
                            .Compile();

        Debug.Assert(yFunc(null) == null);
        Debug.Assert(yFunc('A') == "A");
    }
}

2voto

Ugur Ozturk Points 25

La réponse de Jon Skeet était correcte, dans mon cas j'utilisais DateTime pour ma classe Entity. Quand j'ai essayé d'utiliser quelque chose comme

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

J'ai eu l'erreur

La propriété 'System.DateTime Date' n'est pas définie pour le type 'System.Nullable`1[System.DateTime]' (Paramètre 'propriété')

Donc j'ai dû changer DateTime? pour ma classe entité et

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

0 votes

Cette phrase ne concerne pas l'opérateur de propagation nulle.

0 votes

J'aime comment tu mentionnes que Jon Skeet avait raison, suggérant qu'il est en quelque sorte possible qu'il se trompe. Bien vu!

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X