felipe
felipe

Reputation: 682

Examine IEnumerable "stack" while debugging?

Suppose we have a source IEnumerable sequence:

IEnumerable<Tuple<int, int>> source = new [] {
    new Tuple<int, int>(1, 2),
    new Tuple<int, int>(2, 3),
    new Tuple<int, int>(3, 2),
    new Tuple<int, int>(5, 2),
    new Tuple<int, int>(2, 0),
};

We want to apply some filters and some transformations:

IEnumerable<int> result1 = source.Where(t => (t.Item1 + t.Item2) % 2 == 0)
                                 .Select(t => t.Item2)
                                 .Select(i => 1 / i);
IEnumerable<int> result2 = from t in source
                           where (t.Item1 + t.Item2) % 2 == 0
                           let i = t.Item2
                           select 1 / i;

These two queries are equivalent, and both will throw a DivideByZeroException on the last item.

However, when the second query is enumerated, the VS debugger will let me inspect the entire query, thus very handy in determining the source of the problem.

VS Debugger is useful

However, there is no equivalent help when the first query is enumerated. Inspecting into the LINQ implementation yields no useful data, probably due to the binary being optimized:

LINQ is too optimized

Is there a way to usefully inspect the enumerable values up the "stack" of IEnumerables when not using query syntax? Query syntax is not an option because sharing code is impossible with it (ie, the transformations are non trivial and used more than once).

Upvotes: 2

Views: 954

Answers (1)

Servy
Servy

Reputation: 203824

But you can debug the first one. Just insert a breakpoint on any one of the lambdas and you're free to inspect the values of the parameters or whatever else is in scope.

enter image description here

When debugging you can then inspect the values of (in the case of breaking within the first Where) t, t.Item1, etc.

enter image description here

As for the reason that you can inspect t when performing the final select in your second query, but not your first, it's because you haven't created equivalent queries. The second query you wrote, when written out by the compiler, will not generate something like your first query. It will create something subtly, but still significantly, different. It will create something like this:

IEnumerable<int> result1 = source.Where(t => (t.Item1 + t.Item2) % 2 == 0)
                        .Select(t => new
                        {
                            t,
                            i = t.Item2,
                        })
                        .Select(result => 1 / result.i);

A let call doesn't just select out that value, as the first query you wrote does. It selects out a new anonymous type that pulls out the value from the let clause as well as the previous value, and then modifies the subsequent queries to pull out the appropriate variable. That's why the "previous" variables (i.e. t are still in scope at the end of the query (at both compile time and runtime; that alone should have been a big hint to you). Using the query I provided above, when breaking on the select, you can see the value of result.t through the debugger.

Upvotes: 1

Related Questions