Обеспечение требуемого вызова функции - PullRequest
9 голосов
/ 21 августа 2008

У меня есть класс "Status" в C #, используемый следующим образом:

Status MyFunction()
{
   if(...) // something bad
     return new Status(false, "Something went wrong")
   else
     return new Status(true, "OK");
}

Вы поняли идею. Все абоненты MyFunction должны проверить возвращенный статус:

Status myStatus = MyFunction();
if ( ! myStatus.IsOK() )
   // handle it, show a message,...

Ленивые абоненты могут игнорировать статус.

MyFunction(); // call function and ignore returned Status

или

{
  Status myStatus = MyFunction(); 
} // lose all references to myStatus, without calling IsOK() on it

Возможно ли сделать это невозможным? например исключение броска

В общем : возможно ли написать класс C #, для которого у вас есть для вызова определенной функции?

В C ++ версии класса Status я могу написать тест для некоторого приватного bool bIsChecked в деструкторе и позвонить в несколько звонков, когда кто-то не проверяет этот экземпляр.

Что такое эквивалентная опция в C #? Я где-то читал, что «Вы не хотите деструктора в своем классе C #»

Является ли метод Dispose интерфейса IDisposable параметром?

В этом случае нет неуправляемых ресурсов для освобождения. Кроме того, не определено, когда GC удалит объект. Когда он в конечном итоге будет утилизирован, все еще можно будет узнать, где и когда вы проигнорировали этот конкретный экземпляр статуса? Ключевое слово "using" действительно помогает, но опять же, оно не требуется для ленивых абонентов.

Ответы [ 11 ]

10 голосов
/ 21 августа 2008

Я знаю, что это не отвечает на ваш вопрос напрямую, но если «что-то пошло не так» в вашей функции (неожиданные обстоятельства), я думаю, что вы должны вызывать исключение, а не использовать коды возврата статуса.

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

Исключение может быть пользовательского типа, если это уместно.

Для ожидаемых альтернативных результатов я согласен с предложением @Jon Limjap. Мне нравится тип возвращаемого значения bool и префикс имени метода с помощью «Try», например:

bool TryMyFunction(out Status status)
{
}
7 голосов
/ 21 августа 2008

Если вы действительно хотите потребовать от пользователя получить результат MyFunction, вы можете вместо этого аннулировать его и использовать переменную out или ref, например,

void MyFunction(out Status status)
{
}

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

@ Ян

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

3 голосов
/ 21 августа 2008

Даже System.Net.WebRequest выдает исключение, когда возвращаемый код состояния HTTP является кодом ошибки. Типичный способ справиться с этим - это обернуть его вокруг себя. Вы все еще можете игнорировать код состояния в блоке catch.

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

void MyFunction(Action<Status> callback)
 { bool errorHappened = false;

   if (somethingBadHappend) errorHappened = true;

   Status status = (errorHappend)
                     ? new Status(false, "Something went wrong")
                     : new Status(true, "OK");
   callback(status)

   if (!status.isOkWasCalled) 
     throw new Exception("Please call IsOK() on Status"). 
 }

MyFunction(status => if (!status.IsOK()) onerror());

Если вы беспокоитесь о том, что они вызывают IsOK (), ничего не делая, вместо этого используйте Expression >, а затем вы можете проанализировать лямбду, чтобы увидеть, что они делают со статусом:

void MyFunction(Expression<Func<Status,bool>> callback)
 { if (!visitCallbackExpressionTreeAndCheckForIsOKHandlingPattern(callback))
     throw new Exception
                ("Please handle any error statuses in your callback");


   bool errorHappened = false;

   if (somethingBadHappend) errorHappened = true;

   Status status = (errorHappend)
                     ? new Status(false, "Something went wrong")
                     : new Status(true, "OK");

   callback.Compile()(status);
 }

MyFunction(status => status.IsOK() ? true : onerror());

Или вообще отказаться от класса статуса и заставить их передать одного делегата за успех, а другого за ошибку:

void MyFunction(Action success, Action error)
 { if (somethingBadHappened) error(); else success();
 }

MyFunction(()=>;,()=>handleError()); 
2 голосов
/ 21 августа 2008

Я вполне уверен, что вы не можете получить желаемый эффект в качестве возвращаемого значения из метода. C # просто не может делать некоторые вещи, которые может делать C ++. Однако несколько неприятный способ получить подобный эффект заключается в следующем:

using System;

public class Example
{
    public class Toy
    {
        private bool inCupboard = false;
        public void Play() { Console.WriteLine("Playing."); }
        public void PutAway() { inCupboard = true; }
        public bool IsInCupboard { get { return inCupboard; } }
    }

    public delegate void ToyUseCallback(Toy toy);

    public class Parent
    {
        public static void RequestToy(ToyUseCallback callback)
        {
            Toy toy = new Toy();
            callback(toy);
            if (!toy.IsInCupboard)
            {
                throw new Exception("You didn't put your toy in the cupboard!");
            }
        }
    }

    public class Child
    {
        public static void Play()
        {
            Parent.RequestToy(delegate(Toy toy)
            {
                toy.Play();
                // Oops! Forgot to put the toy away!
            });
        }
    }

    public static void Main()
    {
        Child.Play();
        Console.ReadLine();
    }
}

В очень простом примере вы получаете экземпляр Toy, вызывая Parent.RequestToy, и передавая ему делегат . Вместо того, чтобы возвращать игрушку, метод немедленно вызывает делегата с игрушкой, который должен вызвать PutAway, прежде чем он вернется, иначе метод RequestToy вызовет исключение. Я не претендую на разумность использования этой техники - действительно, во всех примерах «что-то пошло не так» исключение почти наверняка будет лучшей ставкой - но я думаю, что оно подходит как можно ближе к исходному запросу.

1 голос
/ 21 августа 2008

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

1 голос
/ 21 августа 2008

Использование Status в качестве возвращаемого значения напоминает мне о «старых днях» программирования на C, когда вы возвращали целое число ниже 0, если что-то не работало.

Не было бы лучше, если бы вы выбросили исключение, когда (как вы выразились) что-то пошло не так ? Если какой-то «ленивый код» не поймает ваше исключение, вы точно об этом узнаете.

0 голосов
/ 03 октября 2014

Один шаблон, который иногда может быть полезен, если объект, к которому выдается запрос, будет использоваться только одним потоком (*), - это чтобы объект сохранял состояние ошибки и говорил, что в случае сбоя операции объект будет неприменимо до тех пор, пока состояние ошибки не будет сброшено (будущие запросы должны немедленно завершаться сбоем, предпочтительно путем выдачи немедленного исключения, которое включает информацию как о предыдущем сбое, так и новом запросе). В тех случаях, когда вызывающий код может предвидеть проблему, это может позволить вызывающему коду обработать проблему более аккуратно, чем если бы было сгенерировано исключение; проблемы, которые не игнорируются вызывающим кодом, обычно приводят к возникновению исключения довольно скоро после их возникновения.

(*) Если к ресурсу будут обращаться несколько потоков, создайте объект-оболочку для каждого потока и попросите, чтобы запросы каждого потока проходили через свою собственную оболочку.

Этот шаблон может использоваться даже в тех случаях, когда исключений нет, и иногда может быть очень практичным в таких случаях. В целом, однако, некоторые вариации шаблона try / do обычно лучше. У методов есть исключение при сбое, если только вызывающая сторона явно не указывает (используя метод TryXX), что ожидаются сбои. Если звонящие говорят, что сбои ожидаются, но не обрабатывают их, это их проблема. Можно объединить попытку / сделать со вторым уровнем защиты, используя схему выше, но я не уверен, будет ли это стоить затрат.

0 голосов
/ 21 августа 2008

@ Пол, вы можете сделать это во время компиляции с Расширяемый C # .

0 голосов
/ 21 августа 2008

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

0 голосов
/ 21 августа 2008

Было бы неплохо, чтобы компилятор проверял это, а не через выражение. : / Хотя я не вижу способа сделать это ...

...