Почему есть метод, который возвращает bool / int и имеет фактический объект в качестве выходного параметра?

12

Я вижу следующий шаблон кода повсюду в кодовой базе моей компании (приложение .NET 3.5):

bool Foo(int barID, out Baz bazObject) { 
    try { 
            // do stuff
            bazObject = someResponseObject;

            return true;
    }
    catch (Exception ex) { 
        // log error
        return false;
    }
}

// calling code
BazObject baz = new BazObject();
fooObject.Foo(barID, out baz);

if (baz != null) { 
    // do stuff with baz
}

Я пытаюсь обдумать, почему вы сделали бы это вместо того, чтобы Fooметод просто взял идентификатор и возвратил Bazобъект, вместо того, чтобы возвращать значение, которое не используется, и иметь фактический объект как параметр ref или output.

Есть ли какая-то скрытая выгода от этого стиля кодирования, который мне не хватает?

Уэйн Молина
источник
В вашем примере, bazбудучи nullи возвращаемый boolсущество falseявляются не эквивалентны. new BazObject()никогда null, поэтому, если не bazObjectбудет обновлено до того, как Exceptionбудет добавлено Foo, когда falseвозвращено baz, никогда не будет null. Было бы очень полезно, если бы спецификации для Fooбыли доступны. На самом деле, это, пожалуй, самая серьезная проблема, демонстрируемая этим кодом.
Стив Пауэлл
Моя память туманна, так как это было очень давно, но я думаю, что испортил пример и проверял, ложно ли «baz», а не ноль. В любом случае паттерн мне показался архаичным, как будто это было из VB6, и разработчик никогда не пытался улучшить свой код (чего он не делал)
Уэйн Молина

Ответы:

11

Вы обычно используете этот шаблон, чтобы написать код следующим образом:

if (Foo(barId, out bazObject))
{
  //DoStuff with bazobject
}

например, он используется в CLR для TryGetValue в классе словаря. Это позволяет избежать некоторой избыточности, но параметры out и ref всегда казались мне немного беспорядочными

Homde
источник
1
+1, вот почему, хотя я склонен возвращать объект как с логическим значением, так и с объектом, а не возвращать их оба отдельно
pdr
Собираюсь отметить это как ответ, поскольку он действительно объясняет причину, хотя консенсус, кажется, довольно устарел, за исключением определенных обстоятельств.
Уэйн Молина
Этот ответ оправдывает использование этого шаблона. Но если это все над вашей базой кода, это, вероятно, плохая практика, как Шон сторона.
Кодизм
11

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

В .net у нас есть исключения для этой цели. Не должно быть никаких причин следовать этой схеме.

[edit] Очевидно, что это влияет на производительность при обработке исключений. Может быть, это как-то связано с этим. Однако в этом фрагменте кода уже выдается исключение. Было бы чище просто позволить ему двигаться вверх по стеку, пока он не окажется в более подходящем месте.

Шон Эдвардс
источник
Нет. Не могу согласиться с этим. Никогда не используйте исключение для ожидаемого состояния. Например, если вы разбираете строку в int, всегда возможно, что кто-то пропустит непарсируемую строку. Вы не должны бросать исключение в этом случае
pdr
Это не то, что я сказал. Если вы читаете код, который опубликовал OP, явно происходит исключение, но метод перехватывает его и возвращает ложь. Этот вопрос связан с обработкой ошибок, а не с ожидаемыми условиями.
Шон Эдвардс
Да, кажется, что он в основном используется в методах, которые загружают данные из базы данных, например, if (baz.Select())но чаще всего возвращаемое значение просто выбрасывается, и значение проверяется по нулевому или некоторому свойству.
Уэйн Молина
@Sean, посмотрите, что вы говорите, я просто возражаю против фразы «В .NET у нас есть исключения для этой цели». Исключения служат для меня совершенно другой цели
pdr
1
Хорошо, позвольте мне быть ясным. Это правда, что вы не должны просто добавлять логическое значение в каждый метод, чтобы учесть ошибки кодирования недопустимых аргументов. Но если входные данные для метода поступают от пользователя, а не от разработчика, вы должны попытаться обработать входные данные без исключения, если они ошиблись.
фунтовые
2

Учитывая фрагмент кода, это выглядит совершенно бессмысленно. Для меня исходный шаблон кода предполагает, что для BazObject допустимым является случай null, а возврат bool является мерой безопасного определения случая сбоя. Если следующий код был:

// calling code
BazObject baz = new BazObject();
bool result = fooObject.Foo(barID, out baz);

if (result) { 
    // do stuff with baz
    // where baz may be 
    // null without a 
    // thrown exception
}

Это будет иметь больше смысла для меня, чтобы сделать это таким образом. Возможно, это тот метод, который кто-то ранее использовал, чтобы гарантировать передачу baz по ссылке, не понимая, как на самом деле работают параметры объекта в C #.

Джоэл Этертон
источник
Я думаю, что это было написано текущим членом команды. Возможно, это то, что я должен беспокоиться о O_O
Уэйн Молина
@Wayne M: меньше беспокойства, чем потенциальной возможности для обучения :)
Джоэл Этертон
1

Иногда этот шаблон полезен, когда вы, вызывающая программа, не должны заботиться о том, является ли возвращаемый тип ссылочным или типом значения. Если вы вызываете такой метод для извлечения типа значения, этому типу значения потребуется либо недействительное установленное значение (например, double.NaN), либо вам нужен другой способ определения успеха.

Кевин Хсу
источник
Это имеет смысл. Кажется немного странным, но это звучит как веская причина.
Уэйн Молина
0

Идея состоит в том, чтобы вернуть значение, которое указывает, был ли процесс успешным, допуская такой код:

Baz b;
if (fooObject.foo(id, out b)) {
   // do something with b
}
else {
   Error.screamAndRun("object lookup/whatever failed! AAaAAAH!");
}

Если объект не может быть нулевым (что кажется правильным в вашем примере), лучше сделать это следующим образом:

Baz b = fooObject.foo(id);
if (b != null) {
   // do something with b
}
else {
   Error.screamAndRun("object lookup/whatever failed! AAaAAAH!");
}

Если объект может быть нулевым, исключения - это путь:

try {
   Baz b = fooObject.foo(id);
}
catch (BazException e) {
   Error.screamAndRun("object lookup/whatever failed! AAaAAAH!");
}

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

Майкл К
источник
Тем более что код уже выдает исключение.
Дэвид Торнли