2008-08-21 14 views
8

У меня есть «Статус» класс в C#, используется следующим образом:Enforcing требуется вызов функции

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++ я могу написать тест на каком-то частном bool bIsChecked в деструкторе и позвонить в некоторые колокола, когда кто-то не проверяет этот экземпляр.

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

Возможно ли использовать метод Dispose IDisposable interface?

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

+0

В соответствующей заметке я нашел этот вопрос: http://stackoverflow.com/questions/173670/why-is-there-no-raii-in-net – jan 2011-02-07 10:29:01

ответ

2

Я вполне уверен, что вы не можете получить желаемый эффект в качестве возвращаемого значения из метода. 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(); 
    } 
} 

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

+0

Я немного смущен, почему я получил помеченную. .. Я сделал что-то не так? – Weeble 2008-11-04 11:50:15

10

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

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

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

Для ожидается альтернативные результаты, я согласен с предложением @Jon Limjap. Я люблю возвращаемый тип BOOL и предваряя название метода с «Try», а-ля:

bool TryMyFunction(out Status status) 
{ 
} 
0

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

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, а не trhowing exceptions. Если это действительно является Исключительным обстоятельствами, то непременно отбросьте ... но скажите, если вам не удалось открыть устройство, тогда было бы более разумным придерживаться кода возврата.

0

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

1

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

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

3

Даже 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 Статус, 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()); 
7

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

void MyFunction(out Status status) 
{ 
} 

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

@Ian,

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

0

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

1

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

0

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

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

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