[back]
Missing stack trace entries in Release mode assemblies in .Net 4.0 (C#)
Update:

I have since submitted a ticket with Microsoft, along with a sample project to reproduce this. The feedback I got was that it is working as expected, and it is a side effect of method in-lining as an optimization for Release mode assemblies.

While this makes sense, I don't agree that it is okay to live with this disadvantage of possibly the best production code debugging tool, stack traces.


I have recently had to troubleshoot an issue for an application based on Release-compiled .Net 4.0 assemblies. While I was lucky enough to have a stack trace, it was, of course, missing line numbers due to the absence of PDB files. A System.ArgumentException was being thrown because of an attempt to add an item with a duplicate key to a dictionary.

While the stack trace itself was nothing too crazy, it seemed like it was missing entries. I wrote the fragment below to explain (compiled to a Console Application):

int[] numbers = { 1, 1 };


// this will throw as soon as the second insertion is attempted
Dictionary dict = numbers.ToDictionary( x => x );

I then generated both Debug and Release assemblies based on this snippet. I kept the PDB file only for the Debug version.

Here is the stack trace for the Release version, similar to what I was using to analyze the root cause:

(this is the Release version of the stack trace)


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.Enumerable.ToDictionary[TSource,TKey,TElement](
IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector,
IEqualityComparer`1 comparer)
at StackTraceSampleProgram.Program.Main(String[] args)

Consider the following entry from the Release mode stack trace above:

   at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](

IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector,
IEqualityComparer`1 comparer)

It is the only ToDictionary entry, but I do not call this four-parameter ToDictionary extension method overload. So then it must be calling it internally, but where's the one-parameter entry for my one-parameter call to ToDictionary? Not there, let me tell you.

However, the Debug version (which has the associated PDB as well) tells a different story:

(this is the Debug version of the stack trace)


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.Enumerable.ToDictionary[TSource,TKey,TElement](
IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector,
IEqualityComparer`1 comparer)
at System.Linq.Enumerable.ToDictionary[TSource,TKey](IEnumerable`1 source,
Func`2 keySelector)

at StackTraceSampleProgram.Program.Main(String[] args) in
C:\Users\Seb\Desktop\stack\stack-release\stack-release\Program.cs:line 13

The following entry is the missing link:

   at System.Linq.Enumerable.ToDictionary[TSource,TKey](

IEnumerable`1 source, Func`2 keySelector)

This entry appears in the Debug version, but not in the Release version.

Maybe it is just my own ignorance about the inner workings of stack trace reports, but I expected everything to be there. I was lucky in this case that the "hidden" call was just another overload of ToDictionary, so it was easy to guess that it was missing. It may be harder to troubleshot in cases when the missing link is a totally different method, in a totally different type.

It's also worth mentioning that the two ToDictionary overloads being called in this case are defined in the same type, and are both public static; It's not like one of them is special in any way.

To conclude, I hope to save time for anyone who may come across such a situation and wonder what is going on. It seems that you cannot always trust that the stack trace is complete when your assembly was compiled using Release mode. This is either because of a bug in .Net 4.0, or because of intended behaviour that I cannot explain at this time ...