Обеспечение требуемого вызова функции

У меня есть класс «Статус» в 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» действительно помогает, но, опять же, оно не требуется для ленивых абонентов.

21.08.2008 09:57:26
На соответствующую записку я нашел этот вопрос: stackoverflow.com/questions/173670/why-is-there-no-raii-in-net
jan 7.02.2011 10:29:01
11 ОТВЕТОВ
РЕШЕНИЕ

Я совершенно уверен, что вы не можете получить желаемый эффект в качестве возвращаемого значения из метода. 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 вызовет исключение. Я не претендую на разумность использования этой техники - действительно, во всех примерах «что-то пошло не так» исключение почти наверняка будет лучшей ставкой - но я думаю, что оно подходит как можно ближе к исходному запросу.

2
21.08.2008 13:17:05
Я немного запутался, почему меня понизили ... Я сделал что-то не так?
Weeble 4.11.2008 11:50:15
Вы отправили хорошее решение не той проблемы. Запрашивающий не должен делать то, что он на самом деле пытается сделать здесь.
skolima 11.03.2009 14:58:45
Это было бы хорошей причиной, чтобы отметить вопрос , а не ответ, и оставить комментарий о том, почему это плохая идея.
Quinn Taylor 3.03.2010 05:05:42

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

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

Выдаемое исключение может иметь пользовательский тип, если это уместно.

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

bool TryMyFunction(out Status status)
{
}
10
21.08.2008 11:27:32

Вы можете бросить исключение:

throw MyException;


[global::System.Serializable]
        public class MyException : Exception
        {
        //
        // For guidelines regarding the creation of new exception types, see
        //    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp
        // and
        //    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp
        //

        public MyException () { }
        public MyException ( string message ) : base( message ) { }
        public MyException ( string message, Exception inner ) : base( message, inner ) { }
        protected MyException (
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context )
            : base( info, context ) { }
    }

Вышеуказанное исключение полностью настраивается в соответствии с вашими требованиями.

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

0
21.08.2008 10:14:35

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

0
21.08.2008 10:07:18

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

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

1
21.08.2008 10:11:35

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

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

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 <Func <Status, bool >>, а затем вы можете проанализировать лямбду, чтобы увидеть, что они делают со статусом:

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()); 
3
21.08.2008 11:01:48

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

void MyFunction(out Status status)
{
}

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

@Ian,

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

7
21.08.2008 10:28:25

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

0
3.03.2010 05:07:39

@Paul вы можете сделать это во время компиляции с Extensible C # .

0
21.08.2008 10:42:52

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

1
21.08.2008 11:51:53

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

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

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

0
2.10.2014 21:38:10