9

我在 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# 类?

在 Status 类的 C++ 版本中,我可以对析构函数中的一些私有 bool bIsChecked 编写测试,并在有人不检查此实例时敲响一些铃声。

C# 中的等效选项是什么?我在某处读到“你不想在你的 C# 类中使用析构函数”

IDisposable 接口的 Dispose 方法是一个选项吗?

在这种情况下,没有可释放的非托管资源。此外,还不确定GC何时释放该对象。当它最终被处置时,是否仍然可以知道您在何时何地忽略了该特定状态实例?“using”关键字确实有帮助,但同样,懒惰的调用者不需要它。

4

11 回答 11

10

我知道这不能直接回答您的问题,但是如果您的函数中出现“出现问题”(意外情况),我认为您应该抛出异常而不是使用状态返回码。

然后让调用者在可能的情况下捕获并处理此异常,或者如果调用者无法处理这种情况,则允许它传播。

如果合适的话,抛出的异常可以是自定义类型。

对于预期的替代结果,我同意@Jon Limjap 的建议。我喜欢 bool 返回类型,并在方法名称前加上“Try”,例如:

bool TryMyFunction(out Status status)
{
}
于 2008-08-21T10:02:28.637 回答
7

如果您真的想要求用户检索 MyFunction 的结果,您可能希望取消它并使用 out 或 ref 变量,例如,

void MyFunction(out Status status)
{
}

它可能看起来很难看,但至少它确保将一个变量传递给函数,该函数将获取您需要它获取的结果。

@伊恩,

异常的问题在于,如果它发生得太频繁,您可能会为异常花费过多的系统资源。异常确实应该用于异常错误,而不是完全预期的消息。

于 2008-08-21T10:28:25.920 回答
3

当返回的 HTTP 状态码是错误码时,即使 System.Net.WebRequest 也会抛出异常。处理它的典型方法是在它周围包裹一个 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());

或者完全放弃状态类,让它们传递一个代表成功,另一个代表错误:

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

MyFunction(()=>;,()=>handleError()); 
于 2008-08-21T10:23:18.540 回答
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并传递一个委托来获得一个 Toy 的实例。该方法不返回玩具,而是立即使用玩具调用委托,该委托必须在返回之前调用 PutAway,否则 RequestToy 方法将抛出异常。我没有声称使用这种技术是否明智——事实上,在所有“出现问题”的例子中,例外几乎肯定是一个更好的选择——但我认为它与你最初的要求一样接近。

于 2008-08-21T13:17:05.203 回答
1

使用 Status 作为返回值让我想起了 C 编程的“旧时代”,如果某些东西不起作用,则返回一个低于 0 的整数。

如果在(如您所说的那样)出现问题时抛出异常不是更好吗?如果某些“惰性代码”没有捕捉到您的异常,您肯定会知道。

于 2008-08-21T10:11:35.680 回答
1

与其强迫某人检查状态,我认为你应该假设程序员意识到不这样做的风险并且有理由采取这种行动。您不知道将来如何使用该功能,并且设置这样的限制只会限制可能性。

于 2008-08-21T11:51:53.990 回答
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 语句检查状态比抛出异常更有效。如果确实是异常情况,那么一定要扔掉……但是说如果您无法打开设备,那么坚持返回码可能会更谨慎。

于 2008-08-21T10:02:44.530 回答
0

让编译器检查而不是通过表达式肯定会很好。:/虽然看不到任何方法...

于 2008-08-21T10:07:18.187 回答
0

GCC 有一个warn_unused_result非常适合这类事情的属性。也许微软的编译器也有类似的东西。

于 2008-08-21T10:31:08.940 回答
0

@Paul,您可以在编译时使用Extensible C#来完成。

于 2008-08-21T10:42:52.173 回答
0

如果代码向其发出请求的对象将仅由单个线程(*)使用,则一种有时可能会有所帮助的模式是让对象保持错误状态,并说如果操作失败,则对象将不可用,直到错误状态被重置(未来的请求应该立即失败,最好是抛出一个立即异常,其中包括有关先前失败和新请求的信息)。在调用代码碰巧预料到问题的情况下,这可能允许调用代码比抛出异常更干净地处理问题;调用代码没有忽略的问题通常会在它们发生后很快触发异常。

(*) 如果一个资源将被多个线程访问,则为每个线程创建一个包装器对象,并让每个线程的请求通过自己的包装器。

即使在没有异常的情况下也可以使用此模式,并且有时在这种情况下可能非常实用。然而,一般来说,try/do 模式的一些变体通常更好。让方法在失败时抛出异常,除非调用者明确指示(通过使用TryXX方法)预期会失败。如果调用者说失败是意料之中的,但不处理它们,那就是他们的问题。可以使用上述方案将 try/do 与第二层保护结合起来,但我不确定它是否值得付出代价。

于 2014-10-02T21:38:10.043 回答