VB.NET Aaaarrrrggghhh - The missing return statement doesn’t give a compile error

Today I found another reason why i don’t like VB.NET

I forgot (commented out) the Return statement in a function (see below) to test different LINQ operators.
During running and later debugging my application I got strange LINQ runtime errors (1 result set is Nothing) …

Public Function PerformSearch(ByVal dc As myDataContext, _
                                  ByVal searchString As String, _
                                  ByVal memberId As Long) _
                    As IQueryable(Of SearchResult)

        searchString = GetFulltextSearchString(searchString)
       
        Dim q = From fulltextSearch In _
       '  -- SNIP SNIP -- removed LINQ code for blog

        ' Return q
    End Function

Figure: VB.NET function without a return statement

 

After a while I realized that the function doesn’t return nothing! But NO COMPILE ERROR! WTF???

image
Figure: BAD: No compile error

The compiler says: Warning “Function ‘PerformSearch’ doesn’t return a value on all code paths. A null reference exception could occur at run time when result is used.”

I realized that myself after a while :-| 

 

image  
Figure: BAD: Visual Studio shows me a curly line underneath “End Function” – but then I have to mouse hover to actually see the problem

 

The weird thing is, that this is by convention (from MSDN) : If the function/procedure doesn’t assign a value to the function-name AND doesn’t use the “Return” statement the function returns the default value of the return type (=Nothing (null) in my case…)  :-(

My recommendations:

  • Always use the “Return” statement in VB.NET (when I don’t forget it ;-) ) instead of assigning a value to the “procedure name” and having “Exit Function”. Its just easier to read!
  • And to make the execution flow more easier to read, I prefer a “return” statement as last line of a method/function/procedure

See this BAD example

Public Function CalcMyValue(ByVal x As Integer, ByVal y As Integer) As Integer

        Dim returnValue As Integer
        If (x > 100 And y > 365) Then
            Return 1
        Else
            If (x > 100 And y > 365) Then
                returnValue = x * 1000
            Else
                Return -1
            End If
        End If
        returnValue = returnValue * 7
        Return returnValue

    End Function

Figure: Bad example with mix of return statements in the middle 

Note
There is a Visual Studio option per Visual Basic project, to set the “compile warning” to a “compile error”
List with all warnings in VB on MSDN

image
Figure: Why is this an option? and why is the default a “Warning”?? WTF

What is --- End of inner exception stack trace ---

Why is my Stack trace split into 2 half's, delimited by “End of inner exception stack trace “??

Example StackTrace from one of my older applications, that manages GPS points

Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ExceptionHandlingException: The type 'Old.Base.DataAccess.ExceptionHandler.OracleExceptionHandler, Old.DataAccess, Version=2.0.0.11969, Culture=neutral, PublicKeyToken=null' cannot be resolved. Please verify the spelling is correct or that the full type name is provided. ---> System.ArgumentException: The type 'Old.Base.DataAccess.ExceptionHandler.OracleExceptionHandler, Old.DataAccess, Version=2.0.0.11969, Culture=neutral, PublicKeyToken=null' cannot be resolved. Please verify the spelling is correct or that the full type name is provided.
   at Microsoft.Practices.EnterpriseLibrary.Common.Configuration.AssemblyQualifiedTypeNameConverter.ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, Object value)
   at System.ComponentModel.TypeConverter.ConvertFrom(Object value)
   at Microsoft.Practices.EnterpriseLibrary.Common.Configuration.NameTypeConfigurationElement.get_Type()
   at Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder.CustomProviderAssembler`3.Assemble(IBuilderContext context, TConfiguration objectConfiguration, IConfigurationSource configurationSource, ConfigurationReflectionCache reflectionCache)
   at Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder.AssemblerBasedObjectFactory`2.Create(IBuilderContext context, TConfiguration objectConfiguration, IConfigurationSource configurationSource, ConfigurationReflectionCache reflectionCache)
   at Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ExceptionPolicyEntryCustomFactory.Create(IBuilderContext context, ExceptionTypeData objectConfiguration, IConfigurationSource configurationSource, ConfigurationReflectionCache reflectionCache)
   at Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ExceptionPolicyCustomFactory.CreateObject(IBuilderContext context, String name, IConfigurationSource configurationSource, ConfigurationReflectionCache reflectionCache)
   at Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder.ConfiguredObjectStrategy.BuildUp(IBuilderContext context, Type t, Object existing, String id)
   at Microsoft.Practices.ObjectBuilder.SingletonStrategy.BuildUp(IBuilderContext context, Type typeToBuild, Object existing, String idToBuild)
   at Microsoft.Practices.ObjectBuilder.BuilderStrategy.BuildUp(IBuilderContext context, Type typeToBuild, Object existing, String idToBuild)
   at Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder.ConfigurationNameMappingStrategy.BuildUp(IBuilderContext context, Type t, Object existing, String id)
   at Microsoft.Practices.ObjectBuilder.BuilderBase`1.DoBuildUp(IReadWriteLocator locator, Type typeToBuild, String idToBuild, Object existing, PolicyList[] transientPolicies)
   at Microsoft.Practices.ObjectBuilder.BuilderBase`1.BuildUp(IReadWriteLocator locator, Type typeToBuild, String idToBuild, Object existing, PolicyList[] transientPolicies)
   at Microsoft.Practices.ObjectBuilder.BuilderBase`1.BuildUp[TTypeToBuild](IReadWriteLocator locator, String idToBuild, Object existing, PolicyList[] transientPolicies)
   at Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder.EnterpriseLibraryFactory.BuildUp[T](IReadWriteLocator locator, String id, IConfigurationSource configurationSource)
   at Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder.LocatorNameTypeFactoryBase`1.Create(String name)
   at Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ExceptionPolicy.GetExceptionPolicy(Exception exception, String policyName, ExceptionPolicyFactory factory)
   --- End of inner exception stack trace ---
   at Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ExceptionPolicy.GetExceptionPolicy(Exception exception, String policyName, ExceptionPolicyFactory factory)
   at Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ExceptionPolicy.HandleException(Exception exceptionToHandle, String policyName)
   at Old.SLFD.PlanManager.DataAccess.OracleODP.ImageSaverDAO.InsertImage(InsertImageParameter imageParam)
   at Old.SLFD.PlanManager.BusinessLogic.ImageManipulationBL.ImportImages(String elencoPtFilename, String imagefolder)
   at Old.SLFD.PlanManager.WinForms.Controls.CreationAndImportImages.StartImport()
   at Old.SLFD.PlanManager.WinForms.Controls.CreationAndImportImages.toolbarsManager_ToolClick_1(Object sender, ToolClickEventArgs e)
   at Infragistics.Win.UltraWinToolbars.ToolClickEventHandler.Invoke(Object sender, ToolClickEventArgs e)
   at Infragistics.Win.UltraWinToolbars.UltraToolbarsManager.OnToolClick(ToolClickEventArgs e)
   at Infragistics.Win.UltraWinToolbars.UltraToolbarsManager.FireEvent(ToolbarEventIds id, EventArgs e)
   at Infragistics.Win.UltraWinToolbars.ToolBase.OnToolClick()
   at Infragistics.Win.UltraWinToolbars.ButtonToolUIElement.DoClickProcessing(MouseEventArgs e)
   at Infragistics.Win.UltraWinToolbars.ButtonToolUIElement.OnMouseUp(MouseEventArgs e)
   at Infragistics.Win.ControlUIElementBase.ProcessMouseUpHelper(Object sender, MouseEventArgs e)
   at Infragistics.Win.ControlUIElementBase.ProcessMouseUp(Object sender, MouseEventArgs e)
   at Infragistics.Win.Utilities.ProcessEvent(Control control, ProcessEvent eventToProcess, EventArgs e)
   at Infragistics.Win.UltraControlBase.OnMouseUp(MouseEventArgs e)
   at Infragistics.Win.UltraWinToolbars.UltraToolbarsDockArea.OnMouseUp(MouseEventArgs e)
   at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
   at System.Windows.Forms.Control.WndProc(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

Figure: ArgumentException in my code.

Long time ago, I was wondering why?

The obvious reason for this, is that an Exception can hold an InnerException (see property in screenshot).

image
Figure: Reflector showing type: Exception


This happens if the exception is caught by some other code in the middle (e.g. Business layer; see difference between layers and tiers with nice pictures and nice list of pro/contra ) and wrapped in a new exception.

In the example above EnterpriseLibrary catches an exception and wraps it into another exception. Enterprise Library has an Exception Handling Application Block that lets you configure policies for your exceptions.
E.g. Handle “SQLException” globally, log and email the exception, then wrap it into “MyCustomException” depending of the property “SqlException.Errors”
That is something similar we did for System.Data.OracleClient.OracleException

Latest Posts

Popular Posts