A bit longer code BUT a lot shorter to debug (using the logs)

Today I got a phone call from our support tier describing me a failed use case and providing me with the exception from our log.

The exception looked something like this (faked):

   1: Unhandled Exception: System.ArgumentException: An item with the same key has already been added.


   2:    at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)


   3:    at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)


   4:    at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)


   5:    at BetterException.TwoWayDictionary`2.Add(T key, K value) in D:\DavidovitzSVN\Sample\BetterException\Program.cs:line 27


   6:    at BetterException.Program.Main(String[] args) in D:\DavidovitzSVN\Sample\BetterException\Program.cs:line 16




In first look of it I couldn't understand what is the failing area (because I remember that the class uses more than one dictionary) :(



The class that the exception occurs in looks like this:





   1: public class TwoWayDictionary<T, K>


   2: {


   3:  private Dictionary<T, K> m_A2B = new Dictionary<T, K>();


   4:  private Dictionary<K, T> m_B2A = new Dictionary<K, T>();


   5:  public void Add(T key, K value)


   6:  {


   7:   m_A2B.Add(key, value);


   8:   m_B2A.Add(value, key);


   9:  }


  10:  public void Add_2(T key, K value)


  11:  {


  12:   try


  13:   {


  14:    m_A2B.Add(key, value);


  15:   }


  16:   catch (ArgumentException e)


  17:   {


  18:    throw new ArgumentException(string.Format("Dictionary A2B already contain '{0}'", key), e);


  19:   }


  20:   try


  21:   {


  22:    m_B2A.Add(value, key);


  23:   }


  24:   catch (ArgumentException e)


  25:   {


  26:    throw new ArgumentException(string.Format("Dictionary B2A already contain '{0}'", value), e);


  27:   }


  28:  }


  29:  public K GetValueByKey(T key)


  30:  {


  31:   return m_A2B[key];


  32:  }


  33:  public T GetKeyByValue(K value)


  34:  {


  35:   return m_B2A[value];


  36:  }   


  37: }




As you can see the method uses two dictionaries, so without looking at the source (and line of exception) I cant say what is the problematic argument / dictionary.



I have created a new method named "Add_2" which is a simple refactor of the code that I have wrapped every operation on the dictionary with try and catch and a simple new argument exception that gives a context to the problem when it occurs.



So running the following code:





   1: static void Main(string[] args)


   2: {


   3:  TwoWayDictionary<int, string> NameValueDictionary = new TwoWayDictionary<int, string>();


   4:  NameValueDictionary.Add_2(1, "Ran");


   5:  NameValueDictionary.Add_2(2, "Nir");


   6:  NameValueDictionary.Add_2(3, "Revital");


   7:  NameValueDictionary.Add_2(4, "Revital");


   8: }




Will give the following exception:





   1: Unhandled Exception: System.ArgumentException: Dictionary B2A already contain 'Revital' ---> System.ArgumentException: An item with the same key has already bee


   2: n added.


   3:    at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)


   4:    at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)


   5:    at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)


   6:    at BetterException.TwoWayDictionary`2.Add_2(T key, K value) in D:\DavidovitzSVN\Sample\BetterException\Program.cs:line 41


   7:    --- End of inner exception stack trace ---


   8:    at BetterException.TwoWayDictionary`2.Add_2(T key, K value) in D:\DavidovitzSVN\Sample\BetterException\Program.cs:line 45


   9:    at BetterException.Program.Main(String[] args) in D:\DavidovitzSVN\Sample\BetterException\Program.cs:line 16




So now I know the exact failing dictionary and key without looking at the code and you know what even better the support tier can know this also without contacting me (and who knows maybe they will fix it :) )




  • Method "Add_2" is only a simple implementation and its only for explanation purposes (there are other methods to add context to the exception).


  • Make sure that end user doesn't see your exceptions as this is a security issues (in my case the exceptions are logged)


  • You can download the code also here



I think Dictionary class should have done a better job and describe the exception better, Don you?

A bit longer code BUT a lot shorter to debug (using the logs) A bit longer code BUT a lot shorter to debug (using the logs) Reviewed by Ran Davidovitz on 1:23 PM Rating: 5

1 comment:

LeLong37 said...

We having a similar problem, but I think it's deep inside of LINQ somewhere. Not sure if it's an official LINQ bug, but we can't seem to fix it...

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.Web.Query.Dynamic.ClassFactory.GetDynamicClass(IEnumerable`1 properties) at System.Web.Query.Dynamic.ExpressionParser.ParseNew() at System.Web.Query.Dynamic.ExpressionParser.ParseIdentifier() at System.Web.Query.Dynamic.ExpressionParser.ParsePrimaryStart() at System.Web.Query.Dynamic.ExpressionParser.ParsePrimary() at System.Web.Query.Dynamic.ExpressionParser.ParseUnary() at System.Web.Query.Dynamic.ExpressionParser.ParseMultiplicative() at System.Web.Query.Dynamic.ExpressionParser.ParseAdditive() at System.Web.Query.Dynamic.ExpressionParser.ParseComparison() at System.Web.Query.Dynamic.ExpressionParser.ParseLogicalAnd() at System.Web.Query.Dynamic.ExpressionParser.ParseLogicalOr() at System.Web.Query.Dynamic.ExpressionParser.ParseExpression() at System.Web.Query.Dynamic.ExpressionParser.Parse(Type resultType) at System.Web.Query.Dynamic.DynamicExpression.ParseLambda(ParameterExpression[] parameters, Type resultType, String expression, Object[] values) at System.Web.Query.Dynamic.DynamicExpression.ParseLambda(Type itType, Type resultType, String expression, Object[] values) at System.Web.Query.Dynamic.DynamicQueryable.Select(IQueryable source, String selector, Object[] values) at System.Web.UI.WebControls.DynamicQueryableWrapper.Select(IQueryable source, String selector, Object[] values) at System.Web.UI.WebControls.LinqDataSourceView.ExecuteSelectExpressions(IQueryable source, IDictionary`2 whereValues, IOrderedDictionary orderByOrderedValues, IDictionary`2 groupByValues, IDictionary`2 orderGroupsByValues, IDictionary`2 selectNewValues) at System.Web.UI.WebControls.LinqDataSourceView.ExecuteSelectQuery(LinqDataSourceSelectEventArgs selectEventArgs, Object selectResult, Object table, Boolean storeOriginalValues) at System.Web.UI.WebControls.LinqDataSourceView.ExecuteSelect(DataSourceSelectArguments arguments) at System.Web.UI.WebControls.ListControl.OnDataBinding(EventArgs e) at System.Web.UI.WebControls.ListControl.PerformSelect() at System.Web.UI.WebControls.BaseDataBoundControl.DataBind() at System.Web.UI.WebControls.BaseDataBoundControl.EnsureDataBound() at System.Web.UI.WebControls.BaseDataBoundControl.OnPreRender(EventArgs e) at System.Web.UI.WebControls.ListControl.OnPreRender(EventArgs e) at System.Web.UI.Control.PreRenderRecursiveInternal() at System.Web.UI.Control.PreRenderRecursiveInternal() at System.Web.UI.Control.PreRenderRecursiveInternal() at System.Web.UI.Control.PreRenderRecursiveInternal() at System.Web.UI.Control.PreRenderRecursiveInternal() at System.Web.UI.Control.PreRenderRecursiveInternal() at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

Powered by Blogger.