Note that the collection 'collection' is not an option, as it may have more than one element - it is just a plain old collection. The aim of the code in your post is to produce a constructed object "myObj". The TryGet pattern gives a very compact form for doing this particular thing - more compact than using Option<T>. But there are still advantages to using Option<T> in this case, and if you read the discussion after the first example in the article, it should be more clear what some of these advantages are. I'll mention a few here too. One advantage is to avoid the 'out' keyword - 'out' confuses many programmers. Another is that using a good implementation of options like Option<T> makes it impossible in the client code to refer to a value when the value is invalid. In your example the control flow is pretty clear and it's unlikely that someone would accidentally dereference myObj when myObj is null, but it's still possible, especially if later on the control flow were changed to account for more complicated conditions. Also, while many people are familiar with the TryGet pattern, many are not, and to them, an interface that explicitly uses an option will be easier to understand. If you really want to get a sense for what the option pattern can do, and when it is best used, the best way to learn is to try using it in your code. It may seen trivial or peculiar at first, but as you get used to it you should begin to see its usefulness. That is how it was for me, anyway.
The argument that 'out' confuses many programmers is bogus. If the programmer cannot grasp a concept such as the 'out' keyword, I don't want to know all the other things he cannot grasp. Fired.
The second argument, namely that someone can dereference myObj if myObj is null is just as bogus. If the programmer forgets to check the outcome of the operation (either by comparing to null, checking the outcome of GetTry*, or, in your case, entry.IsPresent), the operation will always result in an exception or something equally nonproductive.
As for the argument that using the TryGet* method is cumbersome because it requires two declarations, this is false as well, you are not under any obligation to store the boolean value, since in pretty much all of the cases you will react in a fitting manner in the if-clause following the TryGet* call.
A big disadvantage of your pattern is that you create two different modi operandi where one is sufficient, thus adding to the supposed confusion there is about TryGet*. Perhaps you should have written an article on that instead?
Another thing that might bother people is the fact that you return a composite object (bool + resulting value) after a method call, which is generally frowned upon in OO.
In short, in my eyes, you fail to name any real advantage of your Option over the TryGet-pattern. I am not trying to put you down and I appreciate the effort you put into writing this article, but I feel I would not do you a favor by not telling you this.
I tend to agree; starting with the interpretation of the reference material. A system without null (i.e. a system with "pure" values) is not better, only different. I'll simply state the truism - where would binary be without zero?
I used to think that the argument that many programmers will be confused by 'out' was bogus too. But this is mentioned in [CA06] or [Ric06] or both (I do not remember which offhand) and I am willing to take their word for it in this case. And the 'out' construct is, in any case, more complicated than simply returning a value, once one is familiar with the type of value that is being returned.
Problems with null, if they are not already apparent, should become more apparent later in this series of articles.
Using TryGet requires one declaration and one call - two operations, and usually two lines, in most formatting styles. In the article I say that two declarations are required but this is a bit of a misstatement as it is true that only one declaration is required. The main point about the two-ness of TryGet is that two operations (lines) are required to use the TryGet pattern. I will clarify this in a subsequent version of the article. Thanks for pointing it out.
The point is not to have both TryGet and a method that returns Option<T> - the point is to design the class in the first place so that it returns only Option<T> and does not have a TryGet method. When backward compatibility is an issue and there is already a TryGet method, then of course one may have to make concessions.
Are you saying that returning a composite object like a Point, Color, Nullable<T>, or array is frowned upon?
I don't expect that everyone is going to 'get' the advantages of Option<t>. As I said, they may seem trivial at first, but if you try it in your code I think you will begin to see them over time.
Kudos on the book references, but it doesn't exactly change anything.
My point is that
a) programmers who are having trouble with simple concepts like 'out' are not meant to be programmers and will not be welcome on any team I govern.
b) the number of lines required is exactly the same. Either you use the TryGet*-pattern and you use one line for declaring the out-parameter and one line for calling (and checking) TryGet*, or you use the Option-pattern and use one line for declaring the Option<> variable and calling the function, and one for checking option.IsPresent. Changes nothing.
c) returning a composite object that encapsulate success/failure state is frowned upon, not returning 'any' composite object. If nothing else, this is where exceptions come in.
d) your pattern causes the creation of a new, unneeded object every time it is called, which will result in lower performance and pollution of generation 1.
It's not that I don't 'get' the advantage, it's more that I completely disagree with it. This pattern is actually a step backward from the current state of OO programming languages.
Teams are not all the same - you cannot dismiss the complexity of something as being a problem just because some people understand it. It may not be a problem in your team, but it is a problem in some teams.
The number of lines is the same only when the value is actually used. There are many scenarios where you simply want to check if the value is present in the hash table.
You might ask yourself Dictionary.TryGetValue and Double.TryParse don't use exceptions exclusively to indicate whether the data is present. Double.TryParse was created specifically as an alternative to an exclusively exception-based failure model after Microsoft received feedback about Double.Parse [Ric06].
With reference types, no value is created - all that is required is assigning null to some variable. With value types, the object is created regardless of how it's encapsulated, unless a two-step process is used. TryGetValue creates the object too. Usually this overhead is insigificant anyway; probability of error is generally much more important. Not always, but usually.
-- modified at 11:20 Wednesday 14th February, 2007
With regard to teams, what I was saying that indeed there are programmers that have problems with the 'out' method. It points out a very real problem the software engineering business faces, and that is that of widespread incompetence. For some reason, it's perfectly normal and accepted for hobby programmers to land actual jobs without having some intermediate training. There's alot more to software engineering than knowing your programming language, and when you have problems with even that, you are bound to become a liability for the team your working with. It's nice that you found a way around the 'out' keyword, but the same programmer will introduce serious problems that cannot be solved with a simple workaround.
Your argument that alot of times the object is not used but it's existance in the collection is merely checked is completely bogus. If this is what you're after, use the 'Contains' method of your collection. Please don't make up arguments for the sake of arguments.
Your 3rd paragraph is true, however I fail to see the relevance in this discussion.
As for the extra object that is generated, you fail to see the point.
TryGet*: Failure: one bool returned, no object returned
TryGet*: Success: one bool returned, one object returned
Option: Failure: one additional object constructed, one bool set, no object set.
Option: Success: one additional object constructed, one bool set, one object set.
As you see, in the Option pattern, regardless of whether or not the call succeeds, an additional object (the returned object) is constructed. The fact that you claim this overhead is insignificant compared to the probability of the error shows that you either have little to no real world experience, or that you plain suck at doing performance tests on your code.
What you say about 'out' could be true, but it doesn't change the fact that it will confuse many programmers.
In a class that returns Option<T> instead of using the TryGetValue pattern, there is no need to have a Contains method at all. The functionality of TryGetValue, Contains, and item[xx] are subsumed in one (clear) method. In fact you could make item[xx] return an Option<T> instance, and this might be the best solution of all if we could start from scratch and did not have to worry about conforming to IDictionary<T>.
I mentioned exceptions because you asked about them (3rd paragraph). I'm glad you agree that your original point was wrong.
Option<T> is a struct - instances of Option<T> are passed around on the stack. The fields within the struct have exactly the same overhead as the fields returned by TryGetValue. TryGetValue always returns two values - a boolean value and the 'out' value. This is true regardless of whether the boolean value is true or false. Maybe you do not understand 'out'.
Please keep the discussion objective and refrain from insulting people, or move on.
-- modified at 12:07 Wednesday 14th February, 2007
Accessing option.Value throws an exception when no value is present. If you access the value part of a two-variable system such as what is returned from Dictionary.TryGetValue, you get no indication of an error - execution continues as if nothing had happened. This scenario entails problems even when the value is a reference type, but it's particularly pernicious when the value is a value type.
The whole point of using TryGet* (or the Option pattern, for that matter) is that you are able to handle failures right there instead of continuing your code with an object of which you do not know the state. If you get as far as to use this object lateron eventhough the call has failed, you have design issues.
Last Visit: 31-Dec-99 19:00 Last Update: 3-Mar-24 20:18