Сведения о вопросе

Getthesound

16:58, 10th August, 2020

Теги

Принудительное выполнение вызова требуемой функции

Просмотров: 443   Ответов: 11

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



  Сведения об ответе

PROGA

22:04, 2nd August, 2020

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

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

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

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

bool TryMyFunction(out Status status)
{
}


  Сведения об ответе

lool

19:25, 3rd August, 2020

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

void MyFunction(out Status status)
{
}

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

@Ian,

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


  Сведения об ответе

padenie

11:04, 16th August, 2020

Даже 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>> вместо этого, а затем вы можете проанализировать lambda, чтобы увидеть, что они делают со статусом:

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());

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

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

MyFunction(()=>;,()=>handleError()); 


  Сведения об ответе

SSESION

12:43, 22nd August, 2020

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


  Сведения об ответе

park

21:07, 13th August, 2020

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

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


  Сведения об ответе

piter

03:48, 4th August, 2020

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


  Сведения об ответе

ITSME

10:21, 14th August, 2020

Вы можете создать исключение с помощью:

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


  Сведения об ответе

SKY

06:42, 10th August, 2020

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


  Сведения об ответе

fo_I_K

06:42, 20th August, 2020

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


  Сведения об ответе

VERSUION

04:26, 9th August, 2020

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


  Сведения об ответе

VERSUION

01:19, 9th August, 2020

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

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

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


Ответить на вопрос

Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться