Make Iterator function to return a null reference when an exception is thrown

183 views Asked by At

The following code iterates through a set of processors and returns their ids. Hardware exploration is hazardous and I want to make this function is as stable as possible.

That is:

  • If an exception was thrown whilst preparing the iteration, return a null reference; and
  • If an exception was thrown during an iteration, simply skip this iteration, silently.

Code:

    Public ReadOnly Iterator Property ProcessorIds As IEnumerable(Of String) Implements IHardwareIds.ProcessorIds
        Get

            Try

                Dim BiosQuery As ObjectQuery = New SelectQuery("Win32_Processor") 'https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-processor
                Dim HardwarePieces As New ManagementObjectSearcher(BiosQuery)

                For Each MyInfo As ManagementObject In HardwarePieces.Get
                    Try
                        Dim ProcessorId As String = MyInfo("ProcessorId").ToString
                        Yield ProcessorId
                    Catch ex As Exception
                    End Try
                Next

            Catch ex As Exception
                Return Nothing
            End Try

        End Get
    End Property

The current code does not compile because using return in an iterator is forbidden.

The best I could think of was to replace Return Nothing by Yield Nothing. I expect it should result in an enumerable containing one item, which is a null reference. Is this my only option?

I know I could just use a regular function and return the collection once fully iterated. This is an exercise to practice with Iterators.

2

There are 2 answers

1
djv On BEST ANSWER

I don't see a way to cause an Iterator to return an empty collection upon exception, if that is what you really want to do. If you do, you could stop using Iterator and Yield, and simply do

Public ReadOnly Property ProcessorIds As IEnumerable(Of String) Implements IHardwareIds.ProcessorIds
    Get
        Try
            Using s As New ManagementObjectSearcher(New SelectQuery("Win32_Processor"))
                Return s.Get().OfType(Of ManagementObject).Select(Function(o) o("ProcessorId").ToString())
            End Using
        Catch
            Return Enumerable.Empty(Of String)
        End Try
    End Get
End Property

Using LINQ without the Iterator actually allows you to remove the For Each and simplify the code.

(If you would rather return null, it's almost the same)

Public ReadOnly Property ProcessorIds As IEnumerable(Of String) Implements IHardwareIds.ProcessorIds
    Get
        Try
            Using s As New ManagementObjectSearcher(New SelectQuery("Win32_Processor"))
                Return s.Get().OfType(Of ManagementObject).Select(Function(o) o("ProcessorId").ToString())
            End Using
        Catch
            Return Nothing
        End Try
    End Get
End Property

however, returning null has a different feel that other Enumerable functions (such as Where, which returns an empty collection when the predicate is not satisfied by any element, as pointed out by @Anu6is).

You can also stop iteration upon the first exception. If Yield is never hit, your result is an empty collection. I think this is more in the spirit of Iterator.

Public ReadOnly Iterator Property ProcessorIds As IEnumerable(Of String) Implements IHardwareIds.ProcessorIds
    Get
        Try
            Using s As New ManagementObjectSearcher(New SelectQuery("Win32_Processor"))
                For Each mo In s.Get()
                    Yield mo("ProcessorId").ToString()
                Next
            End Using
        Catch
            Return ' or Exit Property
        End Try
    End Get
End Property

The C# analog is yield break; and it seems quite clean. See also this answer.

0
Craig On

The best way to handle an issue during setup (rather than during iteration) is to have a public non-iterator wrapper function that deals with the setup and then a private iterator function that does the actual iteration.

I've commonly done this for library-ish functions where I want to validate the arguments, and if you read Jon Skeet's EduLinq series, you'll see that he uses this pattern to handle argument validation.

A simplified example is something like this:

Public Function GetFooStream(ByVal someArg As Bar) As IEnumerable(Of Foo)
    If someArg Is Nothing Then Throw New ArgumentNullException(NameOf(someArg))

    Return _GetFooStreamImpl(someArg)
End Function

Private Iterator Function _GetFooStreamImpl(ByVal someArg As Bar) As IEnumerable(Of Foo)
    'someArg guaranteed to be non-null here
End Function

I just show argument validation here, but this could also include necessary up-front setup as well.

(This is intended as general advice for the case where an Iterator is still the best solution; for this specific case, I'd agree with the preference to use Linq instead of writing your own iteration/transformation.)