Iris Classon
Iris Classon - In Love with Code

Stupid question 39: Why can't I return null from a generic method, and what should I return?

[To celebrate my first year of programming I will ask a ‘stupid’ questions daily on my blog for a year, to make sure I learn at least 365 new things during my second year as a developer]

What to return when you dont know what will be returned?

I love generics, but it can get tricky. You might want to return null, but aren’t allowed to do so. The problem is that you can’t return null, since the type used might be a reference type or a value type,- and value types can not be null. There are a few ways to solve this:

  1. Return the default(T) keyword, “The default keyword, will return null for reference types and zero for numeric value types. For structs, it will return each member of the struct initialized to zero or null depending on whether they are value or reference types” - MSDN

  2. You can add a restriction to the method so only reference types are allowed by adding where T : class

  3. If you want to use structs then restrict the method to only allow structs andmake T nullable

How do you do, and what would your recommend? Any best practices here?

Examples:

[sourcecode language=“csharp”]
static T GenericMethod(int id)
{
//something something

        return default(T);  
    }  

    static T GenericMethod1<T>(int id) where T : class  
    {  
        //something something  

        return null;  
    }  

    static T? GenericMethod2<T>(int id) where T : struct  
    {  
        //something something  
        return null;  
    }  

[/sourcecode]

2.

Comments

Leave a comment below, or by email.
Paul
9/10/2012 5:05:02 AM
Reply to: JPollack
Hi there,
thinking about throwing an exception was one of the first things I mentioned.
I assume you are talking about the message envelope class when you are talking about over-engineering. I agree it almost certainly would be I reengineering for a lot of common scenarios, I did try to go to great lengths to say that that to answer the question properly context is needed. The message envelope idea was just another tool in your arsenal for when throwing an exception is not the correct thing to do but when the caller requires more information than just couldn't do it.
I think apart from that your comments are similar to mine and Igors. I think we are perhaps more in agreement than you think.

Kind regards,
Paul 
JPollack
9/10/2012 1:13:28 AM
Reply to: Paul
Paul, is it not a little bit of over-engineering?

The question is: why a return value is missing? Exceptional cases should throw an exception. The rule here is: fail fast.

Otherwise default(T) or NULL is an option too which depends on the context.

Obviously, the returned NULL value might end up with NullReferenceException so make sure to check method's returned value for NULL. 
Igor Khavkin
9/8/2012 7:54:52 AM
Depends on what you expect T to be. And what default(T) (null is default(T) in the cases 2 and 3 too) would mean. If your method returns some value that is expected to be always present, you are likely to not need that return default(T)/null at all - absence of value to return is more likely to be expressed as some Exception (meaning that expected workflow has completly failed, caller absolutely must resort to handling this issue). Otherwise you risk, for example, returning 0 as default(int) and the caller will proceed with the value not expecting it to mean something special.  If your method returns optional value, then you need to distinguish "nothing" from a real value. That would be null in C# (and you have to use T? if T is value type indeed). So I think example 1 is a bit dangerously looking if you cannot guarantee that default(T) is distinguishable by caller from "normal" result.

Still at times you may need default(T). Imagine a method like "bool TryGetValue(TKey key, out TValue value)". It will indicate absense of returned value by returning false but still needs to assign some value to the out parameter. So it may prove to be helpful too when there is no class constraint on T. 
Igor Khavkin
9/8/2012 9:02:19 AM
>> (null is default(T) in the cases 2 and 3 too)
Just a small correction: null is the same as default(T) / default(T?) in cases 2 and 3. 
Paul
9/8/2012 9:13:33 AM
I'm not sure there is a one size fits all solution, as with most coding questions the answer is 'it depends'.
You need to know what makes sense within the context of your specific problem. 

There is also the possibility that you are using null to actually indicate something specific to the calling code. If your context was doing a lookup from a dictionary based on id then you might be using null to represent that value was not found. If in the context of your problem that shouldn't really happen then you might consider throwing an exception instead (http://stackoverflow.com/questions/175532/should-a-retrieval-method-return-null-or-throw-an-exception-when-it-cant-prod)

If the method you are implementing really can refer to anything and throwing an exception does not make sense then it probably has to be option 3 or 1. Depending on if the caller needs to be able to differentiate between the a genuine use of the default value and one you have used just to get the code to compile. 

You could even consider having a class (very similar to Nullable) that could represent the output of your function. 
public class GenericMethodResult
{
    public static readonly NotFound = new GenericMethodResult("Not found");
    public static readonly Missing = new GenericMethodResult("Missing");
    public static readonly Obsolete = new GenericMethodResult("Obsolete");

    public GenericMethodResult(T value)
    { 
         Value = value;
         Found = true;
         Message = "Found";  
    }

    private GenericMethodResult(string message)
    { 
         Found = false;
         Message = message;  
    }

    public T Value {get; private set;}
    public bool Found {get; private set;} //Or alternatively an enum; Found, Missing, Invalid, Obsolete or whatever might be useful to the calling code.
    //Extra parameters that might be useful to calling code
    public string Message {get; private set;}
}
This would allow you to set the value property if you have it but just use one of the precanned failure reasons if your function can't return a real value. Your calling code can branch on the found bool (or enum).

In all other cases you would ideally want to know what scenarios your code was supposed to support and limit the type appropriately. Instead of just limiting T to class you could also consider specifying a base class or interface to indicate the expected type.

Ultimately I'm not sure there is enough context here to even begin to answer your question properly but they're some quick thoughts anyway 
Paul
9/8/2012 9:14:54 AM
Editor swallowed angle brackets on the class, public class GenericMethodResult 


Last modified on 2012-09-07

comments powered by Disqus