C #: un elemento con la stessa chiave è già stato aggiunto, durante la compilazione dell'espressione

c# exception expression expression-trees lambda

Domanda

Ok, ecco una difficile. Spero che qui ci sia un guru delle espressioni che può individuare ciò che sto facendo male qui, perché non lo capisco.

Sto costruendo espressioni che uso per filtrare le query. Per facilitare questo processo ho un paio di Expression<Func<T, bool>> metodi di estensione che rendono il mio codice più pulito e finora hanno funzionato bene. Ho scritto test per tutti tranne uno, di cui ne ho scritto uno per oggi. E quel test fallisce completamente con una ArgumentException con una lunga traccia di stack. E io proprio non capisco. Soprattutto perché ho usato questo metodo per un po 'con successo nelle mie domande!

Ad ogni modo, ecco la traccia dello stack che ottengo durante l'esecuzione del test:

failed: System.ArgumentException : An item with the same key has already been added.
    at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
    at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
    at System.Linq.Expressions.ExpressionCompiler.PrepareInitLocal(ILGenerator gen, ParameterExpression p)
    at System.Linq.Expressions.ExpressionCompiler.GenerateInvoke(ILGenerator gen, InvocationExpression invoke, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.GenerateBinary(ILGenerator gen, BinaryExpression b, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.GenerateUnliftedAndAlso(ILGenerator gen, BinaryExpression b)
    at System.Linq.Expressions.ExpressionCompiler.GenerateAndAlso(ILGenerator gen, BinaryExpression b, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.GenerateBinary(ILGenerator gen, BinaryExpression b, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.GenerateUnliftedOrElse(ILGenerator gen, BinaryExpression b)
    at System.Linq.Expressions.ExpressionCompiler.GenerateOrElse(ILGenerator gen, BinaryExpression b, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.GenerateBinary(ILGenerator gen, BinaryExpression b, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.GenerateInvoke(ILGenerator gen, InvocationExpression invoke, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.GenerateUnliftedAndAlso(ILGenerator gen, BinaryExpression b)
    at System.Linq.Expressions.ExpressionCompiler.GenerateAndAlso(ILGenerator gen, BinaryExpression b, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.GenerateBinary(ILGenerator gen, BinaryExpression b, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.GenerateLambda(LambdaExpression lambda)
    at System.Linq.Expressions.ExpressionCompiler.CompileDynamicLambda(LambdaExpression lambda)
    at System.Linq.Expressions.Expression`1.Compile()
    PredicateTests.cs(257,0): at Namespace.ExpressionExtensionsTests.WhereWithin_CollectionIsFilteredAsExpected()

Il test stesso ha il seguente aspetto e fallisce nell'istruzione Compile:

[Test]
public void WhereWithin_CollectionIsFilteredAsExpected()
{
    var range = new[] { Range.Create(2, 7), Range.Create(15, 18) };

    var predicate = Predicate
        .Create<int>(x => x % 2 == 0)
        .AndWithin(range, x => x)
        .Compile();

    var actual = Enumerable.Range(0, 20)
        .Where(predicate)
        .ToArray();

    Assert.That(actual, Is.EqualTo(new[] { 2, 4, 6, 16, 18 }));
}

Non capisco il messaggio di errore. Ho pensato che potrebbe avere a che fare con il fatto che uso sempre x come nome del parametro, ma non sembra essere di aiuto quando ho provato a scambiarli. Ciò che rende ancora più strano per me è che ho utilizzato questo metodo esatto già da tempo in query di Linq2Sql più grandi e hanno sempre funzionato bene. Quindi nel mio test ho cercato di non compilare l'espressione e utilizzare AsQueryable modo che potessi usarlo su quello. Ma ciò ha fatto ToArray che l'eccezione si verifichi invece sullo ToArray . Che cosa sta succedendo qui? Come posso risolvere questo?

Puoi trovare il codice offensivo e fastidioso nel file zip sotto la riga:


Nota: avevo postato alcuni dei relativi codici qui, ma dopo alcuni commenti ho deciso di estrarre il codice nel proprio progetto che mostra l'eccezione in modo più chiaro. E ancora più importante, che può essere eseguito, compilato e debug.


Aggiornamento: semplificato il progetto di esempio ulteriormente con alcuni suggerimenti di @Mark. Come rimuovere la classe di intervallo e invece solo hard coding singola gamma costante. Aggiunto anche un altro esempio in cui l'utilizzo dello stesso identico metodo funziona davvero bene. Quindi, l'utilizzo del metodo AndWithin rende l'app in crash, mentre l'utilizzo del metodo WhereWithin funziona in realtà. Mi sento praticamente all'oscuro!

Risposta popolare

Ho rifattorizzato i tuoi metodi un po 'per rendere il compilatore un po' più felice:

public static Expression<Func<TSubject, bool>> AndWithin<TSubject, TField>(
    this Expression<Func<TSubject, bool>> original,
    IEnumerable<Range<TField>> range, Expression<Func<TSubject, TField>> field) where TField : IComparable<TField>
{
  return original.And(range.GetPredicateFor(field));
}


static Expression<Func<TSource, bool>> GetPredicateFor<TSource, TValue>
    (this IEnumerable<Range<TValue>> range, Expression<Func<TSource, TValue>> selector) where TValue : IComparable<TValue>
{
  var param = Expression.Parameter(typeof(TSource), "x");

  if (range == null || !range.Any())
    return Expression.Lambda<Func<TSource, bool>>(Expression.Constant(false), param);

  Expression body = null;
  foreach (var r in range)
  {
    Expression<Func<TValue, TValue, TValue, bool>> BT = (val, min, max) => val.CompareTo(min) >= 0 && val.CompareTo(max) <= 0;
    var newPart = Expression.Invoke(BT, param,
                                      Expression.Constant(r.Start, typeof(TValue)),
                                      Expression.Constant(r.End, typeof(TValue)));

    body = body == null ? newPart : (Expression)Expression.OrElse(body, newPart);
  }

  return Expression.Lambda<Func<TSource, bool>>(body, param);
}

Entrambi hanno la restrizione aggiuntiva di IComparable<TValue> (l'unica modifica al primo metodo).

Nel secondo, sto facendo il confronto tramite un'implementazione di Func Expression, notiamo che il func è creato all'interno del loop ... è la seconda aggiunta di questa espressione (cosa che pensa sia la stessa ...) nel vecchio metodo sta esplodendo.

Dichiarazione di non responsabilità: Ancora non capisco perché il tuo metodo precedente non ha funzionato, ma questo approccio alternativo aggira il problema. Fatemi sapere se questo non è ciò che cercate e proveremo qualcos'altro.

Inoltre, i miei complimenti su CHIEDERE una domanda bene, un progetto di esempio è esemplare.



Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché