Home > Enterprise >  How to debug/troubleshoot LINQ Expression in C#.NET?
How to debug/troubleshoot LINQ Expression in C#.NET?

Time:09-17

I am using LINQ Expression in my project, but after compilation of that Expression, it throws following exception:

The given key was not present in the dictionary.,    at System.ThrowHelper.ThrowKeyNotFoundException()
   at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   at lambda_method(Closure , ScanQueryList )
   at System.Linq.Enumerable.<>c__DisplayClass6_0`1.<CombinePredicates>b__0(TSource x)
   at System.Linq.Enumerable.WhereSelectListIterator`2.MoveNext()

I am using Dictionary <double[], List<double>> where obviously double[] is the key. The linq expression I am using fetches the data from above dictionary using double[] as the key. The expression is as follows:

.Call (.Call ($p.<my_dictionary_name>).get_Item(.Constant<System.Double[] > (System.Double[]))).get_Item(0) > 10D

The problem seems to be happening in .Constant<System.Double[]> (System.Double[])) so how can I see the parameters passed in the above which accepts System.Double[] type. Is there any way to debug or troubleshoot this problem?

CodePudding user response:

You didn't provide the code which generates the expression, but using code like the following:

// using static System.Linq.Expressions.Expression

var key = new double[] { 1, 2 };
var dict = new Dictionary<double[], List<double>>() {
    {key, new List<double>{1,2}}
};
var expr = Lambda(
    Property(
        Constant(dict),
        typeof(Dictionary<double[], List<double>>).GetProperty("Item"),
        Constant(key)
    )
);

The first thing I would check is that I'm using the same array instance both when filling the dictionary, and when trying to get back the value. In other words, the following code:

expr = Lambda(
    Property(
        Constant(dict),
        typeof(Dictionary<double[], List<double>>).GetProperty("Item"),
        Constant(new double[] {1,2})
    )
);

will throw an exception when you try to compile and invoke it, even though the values in the two arrays are the same, because the array you're creating while building the expression is a different array than the key used in the dictionary:

Compilation and invocation exception

(Unless you're using a custom comparer in the dictionary which treats two such array instances as the same value.)


To read the value of the constant, you need to drill down into the expression tree, ultimately cast the expression as a ConstantExpression, and then read the Value property.

// Cast the Body of the LambdaExpression to IndexExpression so we can read the Arguments
// Cast the first argument to ConstantExpression so we can read the Value
var value = ((ConstantExpression)((IndexExpression)(expr.Body)).Arguments[0]).Value;
Console.WriteLine(
    ((double[])value).Length
);

The exact casts and property calls you'll use will depend on the precise structure of your expression tree.

Note that while debugging in Visual Studio, you can drill down within the properties without knowing the precise structure ahead of time.

Debugging screenshot


(Disclaimer: I've written the library in question.)

Alternatively, you could use the ExpressionTreeToString library, with code like the following:

// using ExpressionTreeToString;

// expr contains an Expression
Console.WriteLine(
    expr.ToString("Textual tree", "C#")
);

returning a string which describes the expression tree and evaluates the constants.

Lambda (Func<List<double>>)
    · Body - Index (List<double>) = #List<double>
        · Object - Constant (Dictionary<double[], List<double>>) = #Dictionary<double[], List<double>>
        · Arguments[0] - Constant (double[]) = new[] { 1, 2 }

CodePudding user response:

I think the problem is in the fact that a double[] is a reference variable. Passing the proper EqualityComparer when creating the Dictionary will solve this problem.

Debug the code

If you are certain that the requested double[] is in your dictionary, try to debug it as follows, in the code just above where you try to fetch the dictionary item.

Stop in your debugger, and add some test code to debug your problem. In this code get any key from the dictionary, for instance the first one. Create a copy for this key.

Dictionary<double[], List<double>> myDictionary = ...

double[] firstKey = myDictionary.Keys.FirstOrDefault();
double[] testKey = firstKey.ToArray();

Now you know that firstKey and testKey are two different arrays, that have the same length and values. firstKey is a key in the Dictionary. testKey is not, although it is an array that has the same length and values as firstKey. The following test code should pass:

// firstKey and testKey are arrays with the same length and values:
Debug.Assert(firstKey.SequenceEqual(testKey));

// firstKey is in the dictionary
Debug.Assert(myDictionary.Keys.Contains(firstKey));

// testKey is not the same object as firstKey
Debug.Assert(!Object.ReferenceEquals(firstKey, testKey));

Now what do you want with the key that is in your expression? Should the double[] that you want to use as key be the same object, or can it be a different object, but with the same values?

If your requirement says that a different array, but with the same length and values should also find the element, the following should pass:

// Dictionary uses a Comparer to test whether firstKey and testKey are equal:
IEqualityComparer<double[]> comparer = myDictionary.Comparer;
Debug.Assert(comparer.Equals(firstKey, testKey);

It this does not pass, then you know the cause of the problem: the Dictionary uses the incorrect IEqualityComparer<double[]>

Create the EqualityComparer

class DoubleArrayEqualityComparer : EqualityComparer<double[]>
{
    public static IEqualityComparer<double[]> ValueComparer {get;} = new DoubleArrayEqualityComparer();

    public override bool Equals (double[] x, double[] y)
    {
        // return true if you consider x and y to be equal
        if (x == null) return y == null;               // true if both null
        if (y == null) return false;                   // because x not null;
        if (Object.ReferenceEquals(x, y)) return true; // for efficiency:

        // x and y are equal if they have the same length
        // and have the same values in the same order:
        return x.Length == y.Length
            && x.SequenceEqual(y);
    }

Enumerable.SequenceEqual does not check for the length in advance. For efficiency I added the check for the length.

For dictionary a proper GetHashCode is very important. You want a method that returns the same hash code if x and y are the same, but if they are different, a different hash code is not obliged, but it enhances lookup efficiency. However, calculating the hash code should not take too long, otherwise it will deteriorate efficiency.

What kind of hash function you select depends on what kind of double arrays you use as key: are they short arrays, then consider to use all values to calculate the hash. If your key contains 1000s of doubles, then using them all to calculate a hash might be too slow. Maybe you have any idea in which aspect you expect your keys to differ. If most of them start with the same first four values, consider not to use them in your hash function.

public abstract int GetHashCode (double[] x)
{
    if (x == null) return 7844588; // just a number

    // select two large prime numbers:
    const int prime1 = ...;
    const int prime2 = ...;

    unchecked
    {        
        int hash = prime1 * x.Length.GetHashCode();

        foreach (double value in x)
        {
             hash = hash * prime2   value.GetHashCode();
        }
        return hash;
    }
}

Consider to check only the first few elements of the array:

foreach (double value in x.Take(5))
  • Related