20

I am writing a type of Queue implementation that has a TryDequeue method that uses a pattern similar to various .NET TryParse methods, where I return a boolean value if the action succeeded, and use an out parameter to return the actual dequeued value.

public bool TryDequeue(out Message message) => _innerQueue.TryDequeue(out message);

Now, I like to avoid out params whenever I can. C# 7 gives us out variable delcarations to make working with them easier, but I still consider out params more of a necessary evil than a useful tool.

The behaviour I want from this method is as follows:

  • If there is an item to dequeue, return it.
  • If there are no items to dequeue (the queue is empty), provide the caller with enough information to act appropriately.
  • Don't just return a null item if there are no items left.
  • Don't throw an exception if trying to dequeue from an empty queue.

Right now, a caller of this method would almost always use a pattern like the following (using C#7 out variable syntax):

if (myMessageQueue.TryDequeue(out Message dequeued))
    MyMessagingClass.SendMessage(dequeued)
else
    Console.WriteLine("No messages!"); // do other stuff

Which isn't the worst, all told. But I can't help but feel there might be nicer ways to do this (I'm totally willing to concede that there might not be). I hate how the caller has to break up it's flow with a conditional when all it wants to is get a value if one exists.

What are some other patterns that exist to accomplish this same "try" behaviour?

For context, this method may potentially be called in VB projects, so bonus points for something that works nice in both. This fact should carry very little weight, though.

  • 9
    Define an Option<T> struct and return it. Those bool Try(..., out data) functions are an abomination. – CodesInChaos Mar 16 '17 at 17:37
  • 2
    I was thinking similar... Maybe<T>, Option<T> if one is adverse to OUT parameters. – Jon Raynor Mar 16 '17 at 17:38
  • 1
    @FrustratedWithFormsDesigner well, I haven't so much "tried" other things (puns are welcome), so much as thought of them. I had the idea to return a ValueTuple but at best I don't think that offered much improvement. – Eric Sondergard Mar 16 '17 at 17:39
  • @CodesInChaos We're on the same page, that's why I'm here! And I like this idea. If you have the time, would you care to give some more detail in an answer so that I can accept it? – Eric Sondergard Mar 16 '17 at 17:40
24

Use an Option type, which is to say an object of a type that has two versions, typically called either "Some" (when a value is present) or "None" (when there isn't a value) ... Or occasionally they're called Just and Nothing. There are then functions in these types that let you access the value if it is present, test for presence, and most importantly a method that lets you apply a function that returns a further Option to the value if it's present (typically in C# this should be called FlatMap although in other languages it is often called Bind instead... The name is critical in C# because having s method of this name and type let's you use your Option objects in LINQ statements).

Additional features may include methods like IfPresent and IfNotPresent to invoke actions in the relevant conditions, and OrElse (which substitutes a default value when no value is present but is a no op otherwise), and so on.

Your example might then look something like:

myMessageQueue.TryDeque()
    .IfPresent( dequeued => MyMessagingClass.SendMessage(dequeued))
    .IfNotPresent (() =>  Console.WriteLine("No messages!")

This is the Option (or Maybe) monad pattern, and it is extremely useful. There are existing implementations (eg https://github.com/nlkl/Optional/blob/master/README.md) but it isn't hard to to your own either.

(You may wish to extend this pattern so that you return a description of the error cause instead of nothing when the method fails ... This is entirely achievable and is often called the Either monad; as the name implies you can use the same FlatMap pattern to make it easy to work with in that case, too)

  • This is definitely a good option, thanks for the suggestion and your thoughts. I was really looking to explore more ideas here. – Eric Sondergard Mar 16 '17 at 18:37
  • 2
    The nice thing about Option / Maybe is that it is a monad (if implemented as one, of course), which means that it can be safely chained, wrapped, unwrapped, and processed without ever needing to handle the different cases directly, and this chaining and processing can be done with LINQ Query Expressions. – Jörg W Mittag Mar 16 '17 at 18:54
  • 3
    By the way, flatMap / bind is called SelectMany in .NET. – Jörg W Mittag Mar 16 '17 at 18:55
  • 5
    I wonder if it would be a better idea to pick a different name, since by doing this you are breaking Try... naming conventions in .NET (and potentially confusing maintainers). – Bob Mar 16 '17 at 23:48
  • @Bob That's a great point. I agree. – Eric Sondergard Mar 17 '17 at 4:51
11

The given answers are good and I would go with one of them. Consider this answer to be just filling in a few corners with some alternative ideas:

  • Make subclasses of Message called SomeMessage and NoMessage. Dequeue can now return NoMessage if there is no message, and SomeMessage if there is a message. If the callee cares to detect which case they are in, they can easily do so by type inspection. If they don't, well, just make a NoMessage do nothing whenever any of its methods are called, and hey, they get what they asked for.

  • The same as the above, but make Message implicitly convertible to bool (or implement operator true / operator false). Now you can say if (message) and have it be true if the message is good and false if it is bad. (This is almost certainly a bad idea, but I include it for completeness!)

  • C# 7 has tuples. Return a (bool, Message) tuple.

  • Make Message a struct type and return Message?.

  • Hey, thanks for your answer. With this question I was just as much trying to expose myself to different ideas as I was trying to find a solution to my specific problem. Cheers! – Eric Sondergard Mar 20 '17 at 23:12
  • I mean, if your "bad idea" is used by Unity, why can't we use it? – Arturo Torres Sánchez May 24 '18 at 14:54
  • @ArturoTorresSánchez: Your question is "someone else used a really bad programming practice, so why can't I?" The question is incoherent. No one is stopping you from using the same bad programming practices as someone else. You go right ahead. That won't make it a good practice in C#. – Eric Lippert May 24 '18 at 15:34
  • It was tongue in cheek. I guess I do need to use "/s" to mark it. – Arturo Torres Sánchez May 24 '18 at 15:42
  • @ArturoTorresSánchez: This is a question and answer site; I consider questions to be questions that are seeking answers. – Eric Lippert May 24 '18 at 15:44
10

In C# 7 you can use pattern matching to achieve the same in a more elegant way:

if (myMessageQueue.TryDequeue() is Message dequeued) 
{
     MyMessagingClass.SendMessage(dequeued)
} 
else 
{
    Console.WriteLine("No messages!"); // do other stuff
}

In this particular case though, I would probably use events rather than polling the queue repeatedly.

  • 3
    Doesn't that though require TryDequeue to return some marker interface with a Message implementation and some "nothing" implementation? Sure, it's more elegant at the call-site, but you're stuck implementing 3 implementations in the actual code, which itself may be impossible if Message is an existing type. – Telastyn Mar 16 '17 at 18:06
  • 1
    @Telastyn: It also works if it just returns null. You could also use an Option type. – JacquesB Mar 16 '17 at 19:31
  • @JacquesB: but what if null is a valid queueable item? – JBSnorro Apr 22 '17 at 22:37
5

There is nothing wrong with a Try... method (having an out parameter) for a scenario in which failure (no value) is just as common as success.

But, if you insist on doing things differently, you may want to return an empty message and either just send that (not your problem anymore) or postpone the if statement until you really must know whether you have a message with content or not.

Note that someone has to do the test sometime anyway. I would argue the test should be done when and where the question is current. This is at the time of dequeing.

  • You make a good point. You still have to test, no matter what you do. Honestly, I still may be going with out parameters at this point (in fact I'm currently exposing multiple "TryDequeue" implementations right now just to fiddle with each of them). I really just wanted to discuss other options that might be out there. – Eric Sondergard Mar 17 '17 at 4:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.