4

Everywhere I read constantly rehashes the idea of "never return nulls" but what should I return if not null in the cases where a value cannot be found?

Take the following method

List<Customer> GetCustomerListByRoleID(Guid RoleID) {}

In this case (and in most plural methods) its easy to simply return an empty List<Customer> if we can't find our value and we're good.

However in the case of a method like

Customer GetCustomerByID(Guid CustomerID) {}

You do not have the luxury of returning an empty array. Infact all you can do is return a New Customer(); but then you have a potentially uninitialized object (which you still need to check for) or null.

So what would be the recommended alternative to returning a null in the singular method?

Maxim Gershkovich
  • 45,951
  • 44
  • 147
  • 243
  • 1
    check this http://stackoverflow.com/questions/1626597/should-functions-return-null-or-an-empty-object – Misha Ts Mar 03 '12 at 17:53

4 Answers4

10

For the single value case consider the TryGet pattern

bool TryGetCustomerByID(Guid CustomerID, out Customer customer) { }

This clearly expresses the intent that this method can and will fail and makes it much more awkward to ignore the aspect of failure. Dealing with it correctly though produces very readable code.

Customer c;
if (container.TryGetCustomer(id, out c)) {
  ...
} else {
  // Deal with failure
}

Often I like to pair my TryGet APIs with a version that throws for those occasions when the caller knows it must be present else it's a violation of some implicit contract.

Customer GetCustomerByID(Guid id) {
  Customer c;
  if (!TryGetCustomerByID(id, out c)) {
    throw new Exception(...);
  }
  return c;
}
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
0

I think it depends whether asking for a customer which doesn't exist should be seen as an error. The two obvious options are:

  • Return null
  • Throw an exception (CustomerNotFoundException or something like that)

In the case where you're being given a Guid, it feels like there's the clear expectation that that will represent a customer. It's not like it would be normal to try to fetch a customer with a Guid on the offchance that one exists.

You could return a Tuple<Customer, bool> or TryGetCustomer - but to be honest, I think both of those are overly complicated for this situation.

The most important thing is that you document what will happen... in particular, if your code will never return null, state that in the documentation. (Or consider using code contracts to give the same information...)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • It's not the goal of the exception. – lolo Mar 03 '12 at 17:46
  • @lolo: What do you mean? If the lack of a result indicates an error, using an exception seems entirely reasonable to me. (Just like the Dictionary indexer does...) – Jon Skeet Mar 03 '12 at 17:47
  • Suppose if the null returing queries are more in number, throwing exceptions won't slow down the application? – Siva Gopal Mar 03 '12 at 17:51
  • 2
    @lolo: Yes, quite possibly. Why would you have a Guid to pass to a query if you didn't have good reason to believe it corresponded with an actual customer? – Jon Skeet Mar 03 '12 at 17:54
  • @SivaGopal: I would suggest that if this is throwing a lot of exceptions, then performance is likely to be the least of your problems. Why would you be issuing so many queries for invalid customers? (Also, a database query is likely to be rather more expensive than an exception in the first place...) – Jon Skeet Mar 03 '12 at 17:55
  • @lolo: Consider "fetching a customer with a given ID" to be somewhat similar to "reading from a file with a given name" - what would you expect to happen if you tried reading from a file which didn't exist? – Jon Skeet Mar 03 '12 at 17:56
  • 1
    If the file not exist, of course exception need to be thrown. But in general, if you have a serching algorithem and your method not finding the element so its not an exception, its not the goal of an exception. – lolo Mar 03 '12 at 18:07
  • @lolo: The goal of an exception is to stop processing when you reach an error, as soon as it's detected. What kind of *non-error* situation are you envisaging where you have a customer ID GUID, but there's no matching customer? It feels *very* like trying to open a specific file which doesn't exist to me. – Jon Skeet Mar 03 '12 at 18:09
  • Im sorry, i disagree. Imagine you have a garage and in the garage you have cars to managed. The worker wants to find a car that is in the garage so he type the license plate but the car was not found becuase a few minutes ago the owner picked the car. Its ok that the car was not found, its not an error, its not need to be in the system. If the car was in the garage but the system could not find the car -> so there you have -> an error – lolo Mar 03 '12 at 18:19
  • @lolo: That's user input, in a scenario where you're already expecting the possibility of there not being a hit. This customer scenario with a guid feels significantly different to me. – Jon Skeet Mar 03 '12 at 18:48
  • Ok, so my example is not so good for what i meant. But, a serch algorithem that did not found the element is not an exception its part of the regular / excpected scenario of a serch algorithem. – lolo Mar 03 '12 at 20:35
  • @lolo: This isn't a *search* though - it's a lookup. They're different things. And you seem to have forgotten my very first paragraph: **it depends whether asking for a customer which doesn't exist should be seen as an error**. In my view there are situations where something like this *is* an error, and situations where it isn't. You seem to be vehemently denying the possibility that it would *ever* be reasonable to think of this as an error. – Jon Skeet Mar 03 '12 at 21:48
  • Personally I agree with Jon. As far as I see it GetCustomerByID(Guid) is a LOOKUP (As he states) and therefore has an expectation that a result will be returned. Although I don't like the approach of an error it is clear that it can be reasonably argued that this is an error condition. – Maxim Gershkovich Mar 04 '12 at 03:30
0

You couuld define a static Customer object which represents a null customer (i.e. Customer.Name = "NOT_FOUND", and return that object instead of null. Then in the calling method, just compare the returned value with that static customer.

edit: Although I admittedly would rather just return null.

user981225
  • 8,762
  • 6
  • 32
  • 41
0

Why retunring a null is discouraged? The idea is that if function's return type is T, you always get one object of type T, while null is definitely not something of type T, not even zero objects of type T.

In the case of getCustomers(), you get a List<Customer> which is 0 to n Customer objects.

In the case of getCustomer() you'd like 0 or 1 Customer objects. In Haskell, you get ADTs and Maybe for that, in Scala, you have case classes and Option. Wha can you use in C#? Well, there's Maybe implementaion in C#, it's really simple inside.

With Maybe<Customer> getCustomer() you're guaranteed not to pass its result as a null value instead of a Customer value. You are explicitly warned: you can either get a Customer, or no Customer.

Why Maybe works better than null? It's because Maybe is a monad with sound mathemetical foundation, and null is not. List is also a monad, that's what makes it so convenient. With a right approach, monads can be combined in nice ways so that you don't have to check HasValue everywhere. With null, you're stuck with C-style nested ifs for each function result.

Exceptions are also a monad! That's why other answers (correctly) propose to throw an exception. With exceptions, you even get another nice thing typical for functional programming, namely pattern matching (in catch clauses).

9000
  • 39,899
  • 9
  • 66
  • 104
  • I don't see what the advantage is of this Maybe concept? It looks to me that it is a copy of the Nullable construct but seems pointless as I am talking about reference types anyway, so what have I missed? – Maxim Gershkovich Mar 04 '12 at 03:34