Compare and use a return value without first storing it in a variable.



  • Lately I've been having yet another one of my programming related appearance-obsessions, not unlike the time I added all kinds of senseless comments ('Delete a file) to my code because I liked the color green, this time quite amazingly involving one-liners in VB.NET - in a language that inherently discourage such practice (no semicolon line terminaton), no less. Basically it has to do with avoding a return value variable in conditional statements with functions:

        ' Avoid calling Process if DoSomething is NULL
        If DoSomething IsNot Nothing Then
            Process(DoSomething)
        End If

    Fairly pretty, granted, but it's not without problems. Firstly, calling a function twice in a langauge that dosen't automatically support memorization isn't exactly what you would call efficient, and secondly, the code could potentionally break if DoSomething for some reason returns NULL the second time it is called. The correct way is therefore something in the lines of the follwing:

        Dim ReturnValue As Object

        ReturnValue = DoSomething

        If ReturnValue  IsNot Nothing Then
            Process(ReturnValue)
        End If

    Ugh. Not even my comment decoration can beatify that. Fortunately, anything is possible with my superiour intellect, and fixing this only requires using one extra procedure - the TryGet-function (yes, the name sucks):

        Public Function TryGet(Of T)(ByVal Value As T, ByRef Reference As T) As T
            Reference = Value : Return Value
        End Function

    That simplifies the code above into this:

        Dim ReturnValue As Object
        If TryGet(DoSomething, ReturnValue) IsNot Nothing Then
            Process(ReturnValue)
        End If

    But that still isn't good enough. Now, to compress this even further I would most likely have to utilze WTF-y consepts such as in-line declaration, ...:

        If TryGet(DoSomething, (Dim ReturnValue As Object)) IsNot Nothing Then
            Process(ReturnValue)
        End If

    ... or a "last return value"-keyword such as MSSQL's @@intentity (which would also eliminate TryGet) ...

        If DoSomething IsNot Nothing Then
            Process($last)
        End If

    So, what is the best way of doing this? Or, should I just give up on VB.NET and transfer to a language that's more suited for such? What do you think?



  • @Mr. Lurker said:


    So, what is the best way of doing this?

        Dim ReturnValue As Object

        ReturnValue = DoSomething

        If ReturnValue  IsNot Nothing Then
            Process(ReturnValue)
        End If

    is IMO perfectly fine. Easy to read, easy to understand. I see no reason to over-complicate that. 



  • I see how it might get annoying in a large procedure if you have to to introduce a procedure wide variable for a single instruction. But I don't think there is a better way to do this in VB. You probably should just try to keep your procedures small as usual... For better readability, declare the variable above the if block and not at the beginning of the procedure. (If you don't already do.)



  • I
    don't think that $last solution (if it existed) would be a good idea though.
    That smells too much of all-purpose global variables. Imagine you combined two DoSomething() functions with a logical operator.
    Which value should go into $last? the result of the combination? That
    would be logical but useless. The result of the first called function?
    But what about the second one? And so on...

    And I didn't start to think about short-cutting operators and multiple threads even...

    I don't think that all this hassle would make up for the benefits.



  •     Dim ReturnValue As Object

        ReturnValue = DoSomething

        If ReturnValue  IsNot Nothing Then
            Process(ReturnValue)
        End If

    Ugh. Not even my comment decoration can beatify that.

    Nonsense. It's perfectly good, readable code.

    Remember, after code has been written, only 1 thing, and 1 thing alone, can happen to it: maintenance.

    Writing slightly slower or more verbose code is perfectly acceptable if it improves maintanability.



  • Yeah, I guess you're right. The best solution is usually the simplest and most straight-forward one. And no, I didn't seriously consider my last wacky "global variable"-approach (I called it WTF-y), as even I could see the potential problems with it.

    And on a last note, the in-line declaration should probably be more direct (is this possible in other languages, by the way?) like so:

        If (Dim ReturnValue = DoSomething) IsNot Nothing Then
            Process(ReturnValue)
        End If

    Now of course, that also somewhat suffers from diminished readability, but it is most likely unavoidable (if implemented), as the overloaded nature of the assignment operator necessitate a separation in context from the equality operator, here using the declaration keyword.

    However, I would say there are some pretty reasonable applications for the TryGet-method, in particular the DoUntil-loop:

        Dim Line As String

        Using Reader As New IO.StreamReader("C:\Test.txt")
            Do Until TryGet(Reader.ReadLine, Line) Is Nothing
                Console.WriteLine(Line)
            Loop
        End Using

    Doing this without TryGet would translate to the following:

        Do
            Line = Reader.ReadLine
            If Line IsNot Nothing Then
                Console.WriteLine(Line)
            Else
                Exit Do
            End If
        Loop

    I do actually find the former example easier to read, given that you're aware of TryGet and its workings; at least it's more ascetically pleasing. But yeah, it's still problematic in terms of both maintainability and efficiency. I think I will try to avoid TryGet in the future.


    Thanks for your time. 



  • I suppose you could do this (I bolded the important bits)...  Though your description of your problem is confusing...

    Public Class Form1
        Private Delegate Function DoSomethingDelegate() As Object

        Private Function DoSomething()
            'Return Nothing
            Return New Object()
        End Function

        Private Function HasValue(ByVal del As DoSomethingDelegate, ByRef value As Object) As Boolean
            value = del()
            Return Not value Is Nothing
        End Function

        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            Dim value As Object = Nothing

            If HasValue(AddressOf DoSomething, value) Then
                Process(value)
            End If
        End Sub

        Private Sub Process(ByVal thing As Object)
            MessageBox.Show("Processing thing...")
        End Sub
    End Class



  • I'm not sure of the context in which you're using this stuff, but the thought occurs you could get around this problem by just saying:

    Process(DoSomething)

    And then have Process itself do the "Is it nothing?" check.

    I'd argue in certain situations this would be a fine approach, assuming that the purpose of Process is "Process the results of functions", after all if Nothing is a result, then why not let the function process that too. Then again if you have this issue with lots of different "Process" functions all over the place, then it's probably best to avoid this approach, since having:

    If Parameter IsNotNothing Then
       ' Whatever
    Endif

     
    wrapped around the code in every function would be horrible.

    Otherwise I'm with ammoQ on this one, you're first attempt isn't broken, so there's no need to fix it.
     



  • I don't really see how using Delegates would improve this in any way. In fact, seeing that it doesn't support parameters and testing against something else than uninitialized return objects (Is Nothing), I would argue that the TryGet-method is superior in both efficiency and versatility.

    My problem is, if you would call it a problem, that I'd simply like to use conditioning on a return value whilst also keeping the possibility of using it, but avoiding variables because they tend to add up in the procedures. The .NET-framework does contain native functions that are designed to do just that, like the TryParse-functions of the different datatypes or TryGetValue, but I just though a generic function to accomplish this it would generally be a good idea.

    @Devi said:

    And then have Process itself do the "Is it nothing?" check.

    That wont work with library functons unless I encapsulate them, and besides - I don't really overcome the problem if i need to break out of an loop like in the ReadLine-example. Hm, it might work to abstract the test away using an iterator, but then I wouldn't be using ReadLine in the first place.
     



  • First way is the best way.  Declare a variable, assign, then check.  No need to create WTFs.


Log in to reply