locked
Generics not behaving as expected during debugging RRS feed

  • Question

  •  
    In order to simplify testing code where I expect an exception to be thrown, I have written a simple generic method, as shown below:

            public static void EnsureTaskThrowsException<T>(Action Task, string message)  
                where T : Exception  
            {  
                try 
                {  
                    Task();  
                    Assert.Fail("Exception was expected");  
                }  
                catch (T ex)  
                {  
                    Assert.AreEqual(message, ex.Message);  
                }  
                catch (Exception ex)  
                {  
                    var expectedType = typeof(T);  
                    var actualType = ex.GetType();  
                    Assert.Fail(string.Format("Wrong Exception type thrown. Expected {0} got {1}", expectedType.AssemblyQualifiedName, actualType.AssemblyQualifiedName));  
                }  
            }  
     

    This works for all framework exceptions, but when I start to use my own exception type, defined in another assembly, then although the expected and actual type appear to be the same, the exception is caught by the catch-all rather than the specific catch block.

    so when I use code such as this:
                ExceptionUtilities.EnsureTaskThrowsException<DataAccessException>(  
                    () => HomeDAO.RetrieveApplicants(dataContext, 1, out proposer, out applicant), "usp_HouseProposer_get returned null for customerActivityId '1'");  
     


    the "wrong" type is caught, with the message below:

    Assert.Fail failed. Wrong Exception type thrown.
    Expected
    Confused.Common.DataAccess.DataAccessException, Confused.Common.DataAccess, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
    got
    Confused.Common.DataAccess.DataAccessException, Confused.Common.DataAccess, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

    Interestingly, if I replace the above call, in the exact same spot, with the following non-generic code, it works!


                try 
                {  
                    HomeDAO.RetrieveApplicants(dataContext, 1, out proposer, out applicant);  
                }  
                catch (DataAccessException ex)  
                {  
                    Console.WriteLine(ex);  
                }  
                finally 
                {  
                    dataContext = null;  
                }  
     

    Can anyone explain why the generic version does not catch the "correct" exception? Why does it think the type of exception is different, even through the fully qualified assembly name is the same?

    Cheers,
        Steve
    Wednesday, January 21, 2009 12:30 PM

All replies

  •  This is a question for Michael Taylor. He might be the only one to answer it. You may either try to post in Common Language Runtime where he more likely to dwell or simply post there a brief note with a link to this post asking for his or someone elses in that forum help. They will come here and read it.
    AlexB
    Wednesday, January 21, 2009 2:36 PM
  • Thanks for that. I will do.

    Some more interesting information, If i leave the test running under the default host (test run config, hosts), then it passes when run outside of debug mode, even using the generic method. Of course running the test in the IDE, it fails as described above.

    If you change the default host to "VSS IDE" and run the test again, outisde of debug, then it fails, so it seems related to the IDE?

    Wednesday, January 21, 2009 3:18 PM
  • Some more info, I refactored the code to use a runtime exception type, InvalidOperationException. However, even using this generates:


    Assert.Fail failed. Wrong Exception type thrown.
    Expected
    System.InvalidOperationException, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
    got
    System.InvalidOperationException, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

    I am really struggling to understand how this can be!

    Cheers,
        Steve
    Thursday, January 22, 2009 8:30 AM
  • Gheh this is a fun one, I changed your code around to the following so I could actually run it.

    using System;  
    using System.Collections.Generic;  
    using System.Text;  
     
    namespace ConsoleApplication49  
    {  
        class Test  
        {  
            static void Process()  
            {  
                throw new InvalidOperationException();  
            }  
     
            public void EnsureTaskThrowsException<T>(Action Task) where T : Exception  
            {  
                try 
                {  
                    Task();  
                    Console.WriteLine("Exception was expected");  
                }  
                catch (T ex)  
                {  
                    Console.WriteLine("Caught {0}!",ex.GetType());  
                }  
                catch (Exception ex)  
                {  
                    var expectedType = typeof(T);  
                    var actualType = ex.GetType();  
                    if (actualType.Equals(expectedType))  
                    {  
                        Console.WriteLine("{0} Caught in wrong handler", ex.GetType());  
                    }  
                    else 
                    {  
                        Console.WriteLine("Wrong Exception type thrown. Expected {0} got {1}", expectedType.AssemblyQualifiedName, actualType.AssemblyQualifiedName);  
                    }  
                }  
            }  
     
            public void runTest()  
            {  
                EnsureTaskThrowsException<InvalidOperationException>(  
                 () => Process());     
        
     
            }  
        }  
     
        class Program  
        {  
            static void Main(string[] args)  
            {  
                Test tst = new Test();  
                tst.runTest();  
                Console.ReadKey();  
            }  
        }  
    }  
     

    Now here's the real fun part, having the debugger present somehow triggers your error, but only in the debug configuration:

    Configuration        Debugger        Result
    Debug                   No                   Caught System.InvalidOperationException!
    Debug                   Yes                  System.InvalidOperationException Caught in wrong handler
    Release                 No                   Caught System.InvalidOperationException!
    Release                 Yes                  Caught System.InvalidOperationException!

    Feels like a bug to me, but if its by design I'd love to know why :)


    Thursday, January 22, 2009 2:36 PM
  • ^^^ Your worked example illustrates the problem well. If it's a bug, how do we escalate it?

    Friday, January 23, 2009 8:47 AM
  • I have a similar example that shows the issue in Visual Studio 2008 SP1 running on "Windows Server 2008" but that behaves as expected on "Windows Server 2008 R2" (aka. Windows 7 Beta 1). So it is possibly a known bug.

    The code that illustrates the problem for me is:

    1   private static TException ExpectExceptionHelper<TException>( GenericDelegate del, bool allowDerivedExceptions )  
    2             where TException : Exception  
    3         {  
    4             try 
    5             {  
    6                 del();  
    7                 Assert.Fail( "Expected exception of type " + typeof( TException ) + "." );  
    8                 throw new Exception( "can't happen" );  
    9             }  
    10            catch ( TException e )  
    11             {  
    12                 if ( !allowDerivedExceptions )  
    13                 {  
    14                     Assert.AreEqual( typeof( TException ), e.GetType() );  
    15                 }  
    16                 return e;  
    17             }  
    18        } 

    When called with a typeparam <ArgumentNullException> and a delegate that thorws an ArgumentNullException then the catch at line 10 does not work when compiled and run in debug mode on "Windows Server 2008" but does work correctly in the current trial version of "Windows Server 2008 R2" (aka. Windows 7).

    --philip.

    Friday, February 27, 2009 1:00 PM