sapbucket
sapbucket

Reputation: 7215

Return type T can't be returned as null? C# Generics

I have a method that generically deserializes a stored object from a users provided filepath and object type. The method works fine, except for when the user provides an invalid filepath. I would like my method to return a null in this case, but when I attempt to return null I get a compilation error. I tried to use a nullable type but get a compilation error. Instead, I typecast an object and return that, but it causes a runtime error. I would like to know if anyone knows the proper way to allow for returning a null. The code is as follows:

        public static T RestoreObj<T>(string datafile)
    {


        try
        {
            var fs = File.OpenRead(datafile);
            var bf = new BinaryFormatter();
            var obj = (T) bf.Deserialize(fs);
            fs.Close();
            return obj;
        }
        catch (Exception e)
        {
            MessageBox.Show("Could not load. Accepts valid *.dom files only. " + e);

            // TODO: how to do this? this will throw a runtime error, and if null returned, a compilation error
            var o = new object();
            return (T) o;
        }
    }

After taking Eric Lippert's quality comments in to consideration I revised the method to look like what you see below. The advantage of using 'using' is that it automatically generates a try..finally block that will call the dispose method (FileStream implements IDisposable, if it did not their would be a compile error). Another nice thing is that the exception thrown is relevant to whatever is actually happening instead of what I have above.

        public static T RestoreObj<T>(string datafile) 
    {
        using (var fs = File.OpenRead(datafile))
        {
            var bf = new BinaryFormatter();
            var obj = (T)bf.Deserialize(fs);
            return obj;
        }
    }

Upvotes: 10

Views: 12248

Answers (7)

Sarfaraz Nawaz
Sarfaraz Nawaz

Reputation: 361722

Put constraint on T as:

 public static T RestoreObj<T>(string datafile) where T : class
                                               //^^^^^^^^^^^^^^

This mean that you can call this method with T which is a reference type which can be null. You cannot call this method when T is a value type, though you call it when T is Nullable<V>:

class  R {} //Reference type!
struct V {} //Value type!

 Xyz.RestoreObj<R>("abc"); //ok
 Xyz.RestoreObj<V>("abc"); //compilation error
 Xyz.RestoreObj<Nullable<V>>("abc"); //ok

Upvotes: 1

Damon
Damon

Reputation: 109

You can use the following to check to see if the file exists and return the default for the type.

            if (!File.Exists(FilePath))
            return default(T);

By using this you will get the default for the type. For example, if you give it a type of 'int' it will return 0 instead of null.

Upvotes: 0

Eric Lippert
Eric Lippert

Reputation: 660463

I would solve the problem by not writing that code in the first place.

A method should do one thing and do it well; you are mixing up deserialization code with error reporting code.

Don't do that. A better way would be to have the deserialization method throw an exception, and write different code that handles the exceptions and reports errors to the user.

More generally, it is dangerous to have a method that eats exceptions and then returns bogus data. That is just creating problems down the line when unsuspecting code that calls your method expects to get good data back.

While we're on the subject of code quality, you should be using "using" blocks to ensure that the file handles are closed if an exception happens. Do not explicitly do a fs.Close() -- rather, do using(var fs = ... ) and let the compiler generate the disposal that closes the file.

Upvotes: 11

Achim
Achim

Reputation: 15722

If you declare your method like this, you should be able to return null:

public static T RestoreObj<T>(string datafile) : where T class

T could also be a struct, which cannot be null. If you restrict T to be a class, it has to be a reference type and you are allowed to return null.

Upvotes: 1

James Michael Hare
James Michael Hare

Reputation: 38427

You can use default(T) instead of null which will be null for reference type and the default value for value types.

Upvotes: 4

Justin Niessner
Justin Niessner

Reputation: 245479

Not all types can be set to null.

You have to constrain T:

public static T RestoreObj<T>(string datafile) where T : class

Your other option (if you're not strictly working with classes) is to return default(T) rather than null.

Upvotes: 2

dlev
dlev

Reputation: 48596

If you're only going to work with classes, then add the where T : class constraint:

public static T RestoreObj<T>(string datafile) where T : class

If you expect to deserialize structs as well, then just return default(T). That will be null for reference types, and the default value (usually 0) for structs. As @JMH points out, default(Nullable<T>) is a null-containing nullable.

Upvotes: 21

Related Questions