Вопрос

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

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#?Я где-то читал: «Вам не нужен деструктор в вашем классе С#»

Можно ли использовать метод Dispose интерфейса IDisposable?

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

Это было полезно?

Решение

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

Другие советы

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

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

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

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

bool TryMyFunction(out Status status)
{
}

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

void MyFunction(out Status status)
{
}

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

@ Ян,

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

Даже System.Net.WebRequest выдает исключение, когда возвращаемый код состояния HTTP является кодом ошибки.Типичный способ справиться с этим — обернуть вокруг него try/catch.Вы по-прежнему можете игнорировать код состояния в блоке 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()); 

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top