Тип возврата T не может быть возвращен как null? С# Generics

У меня есть метод, который в общем случае десериализует сохраненный объект из предоставленного пользователями пути и типа объекта. Метод работает нормально, за исключением случаев, когда пользователь предоставляет недопустимый путь к файлу. Я хотел бы, чтобы мой метод возвращал null в этом случае, но когда я пытаюсь вернуть null, я получаю ошибку компиляции. Я попытался использовать тип с нулевым значением, но получаю ошибку компиляции. Вместо этого я запускаю объект и возвращаю его, но он вызывает ошибку времени выполнения. Я хотел бы знать, знает ли кто-нибудь правильный способ разрешить возврат null. Код выглядит следующим образом:

        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;
        }
    }

После рассмотрения комментариев качества Eric Lippert к рассмотрению я пересмотрел метод, чтобы он выглядел так, как вы видите ниже. Преимущество использования "использования" заключается в том, что он автоматически генерирует блок try..finally, который будет вызывать метод dispose (FileStream реализует IDisposable, если это не их ошибка компиляции). Еще одна приятная вещь заключается в том, что брошенное исключение относится к тому, что на самом деле происходит, а не к тому, что у меня выше.

        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;
        }
    }

Ответ 1

Я бы решил проблему, не написав этот код в первую очередь.

Метод должен делать одно и делать это хорошо; вы смешиваете код десериализации с кодом сообщения об ошибках.

Не делай этого. Лучше всего было бы, чтобы метод десериализации выдавал исключение и записывал другой код, который обрабатывает ошибки и ошибки отчетов пользователю.

В более общем плане, опасно иметь метод, который ест исключения и затем возвращает фиктивные данные. Это просто создает проблемы на линии, когда ничего не подозревающий код, который вызывает ваш метод, ожидает получения хороших данных.

Пока мы говорим о качестве кода, вы должны использовать "использование" блоков, чтобы гарантировать, что дескрипторы файлов будут закрыты, если произойдет исключение. Не делайте явно fs.Close() - скорее, do using(var fs = ... ), и пусть компилятор генерирует удаление, которое закрывает файл.

Ответ 2

Если вы собираетесь работать только с классами, добавьте ограничение where T : class:

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

Если вы также хотите десериализовать структуры, просто верните default(T). Это будет null для ссылочных типов, а значение по умолчанию (обычно 0) для структур. Как указывает @JMH, default(Nullable<T>) является null -содержащим значением, допустимым.

Ответ 3

Вы можете использовать default(T) вместо null, который будет null для ссылочного типа и значения по умолчанию для типов значений.

Ответ 4

Не все типы могут быть установлены на null.

Вам нужно ограничить T:

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

Другой вариант (если вы не строго работаете с классами) должен возвращать default(T), а не null.

Ответ 5

Ограничьте ограничение на T следующим образом:

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

Это означает, что вы можете вызвать этот метод с помощью T, который является ссылочным типом, который может быть null. Вы не можете вызвать этот метод, если T - тип значения, хотя вы его вызываете, когда T 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

Ответ 6

Если вы объявите свой метод следующим образом, вы должны вернуть null:

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

T также может быть структурой, которая не может быть null. Если вы ограничиваете T классом, он должен быть ссылочным типом, и вам разрешено возвращать null.

Ответ 7

Вы можете использовать следующее, чтобы проверить, существует ли файл и вернуть значение по умолчанию для этого типа.

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

Используя это, вы получите значение по умолчанию для этого типа. Например, если вы укажете ему тип 'int', он вернет 0 вместо null.