我在C#中有一个"状态"类,用法如下:
1 2 3 4 5 6 7
| Status MyFunction()
{
if(...) // something bad
return new Status(false,"Something went wrong")
else
return new Status(true,"OK");
} |
你明白了。
MyFunction的所有调用者都应检查返回的状态:
1 2 3
| Status myStatus = MyFunction();
if ( ! myStatus.IsOK() )
// handle it, show a message,... |
但是,惰性呼叫者可以忽略状态。
1
| MyFunction(); // call function and ignore returned Status |
要么
1 2 3
| {
Status myStatus = MyFunction();
} // lose all references to myStatus, without calling IsOK() on it |
是否有可能做到这一点?例如抛出异常
通常:是否可以编写必须在其上调用某个函数的C#类?
在Status类的C ++版本中,我可以对析构函数中的一些私有布尔bIsChecked编写测试,并在有人不检查该实例时响起一些钟声。
C#中的等效选项是什么?
我在某处读到"您不希望在C#类中使用析构函数"
IDisposable接口的Dispose方法是否是一个选项?
在这种情况下,没有非托管资源可以释放。
此外,还不能确定GC何时处置对象。
当它最终被处置时,是否仍可能知道您何时忽略了该特定Status实例?
" using"关键字确实有帮助,但同样,懒惰的调用者不需要它。
我知道这不能直接回答您的问题,但是如果函数中出现"问题"(意外情况),我认为您应该抛出异常,而不是使用状态返回码。
然后将其留给调用方来捕获并处理此异常(如果可以),或者允许它在调用方无法处理这种情况时进行处理。
如果合适,抛出的异常可以是自定义类型。
对于预期的替代结果,我同意@Jon Limjap的建议。我喜欢布尔返回类型,并在方法名称前加上" Try",例如:
1 2 3
| bool TryMyFunction(out Status status)
{
} |
如果您确实要要求用户检索MyFunction的结果,则可能要使其无效,并使用out或ref变量,例如,
1 2 3
| void MyFunction(out Status status)
{
} |
它看起来很丑陋,但至少可以确保将变量传递到函数中,该变量将提取您需要的结果。
@伊恩
异常的问题在于,如果这种情况发生得太频繁,则可能会花费过多的系统资源来处理异常。确实应该将异常用于异常错误,而不是完全预期的消息。
当返回的HTTP状态代码是错误代码时,即使System.Net.WebRequest也会引发异常。处理它的典型方法是在其周围包装try / catch。您仍然可以忽略catch块中的状态代码。
但是,您可以具有Action 的参数,以便强制调用方传递一个接受状态的回调函数,然后检查是否调用了该状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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 >,然后可以分析lambda来查看它们对状态的处理方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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()); |
或者完全放弃状态类,让他们传递一个代表成功的委托,而另一个传递一个错误的委托:
1 2 3 4 5
| void MyFunction(Action success, Action error)
{ if (somethingBadHappened) error(); else success();
}
MyFunction(()=>;,()=>handleError()); |
我相当确定您无法从方法中获得所需的效果作为返回值。 C#不能做C ++可以做的某些事情。但是,以下是获得类似效果的难看方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| 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方法将引发异常。我没有声称使用这种技术的智慧-确实在所有"出了点问题"的例子中,例外肯定是更好的选择-但我认为它与您的原始请求越接近越好。
我认为您不应该强迫某人检查状态,而应该假设程序员意识到不这样做的风险,并有理由采取这种行动。您不知道该功能在将来如何使用,并且这样的限制只会限制可能性。
将Status用作返回值使我想起了C编程的"过去",如果某些事情不起作用,则返回小于0的整数。
如果(如您所说的)出现问题,如果引发异常,会更好吗?如果某些"惰性代码"没有捕获您的异常,那么您肯定会知道。
如果仅向单个线程(*)使用要向其发出代码的请求的对象,则一种可能有时会有用的模式是使对象保持错误状态,并说如果操作失败,则对象将无法使用直到错误状态被重置(将来的请求应该立即失败,最好抛出立即异常,该异常包括有关先前失败和新请求的信息)。如果调用代码碰巧预料到了问题,那么与抛出异常相比,调用代码可以更干净地处理问题。通常,调用代码不会忽略的问题通常会在发生后不久立即触发异常。
(*)如果一个资源将被多个线程访问,请为每个线程创建一个包装对象,并使每个线程的请求通过其自己的包装器。
即使在没有例外的情况下,这种模式也可以使用,在这种情况下有时可能非常实用。但是,通常来说,尝试/执行模式的某些变化通常会更好。让方法在失败时引发异常,除非调用者通过使用TryXX方法显式指示预期失败。如果呼叫者说失败是预期的,但不能解决,那就是他们的问题。可以使用上述方案将try / do与第二层保护结合起来,但是我不确定这是否值得。
@Paul,您可以在编译时使用可扩展C#来实现。
GCC具有warn_unused_result属性,非常适合此类情况。也许Microsoft编译器有类似的东西。
让编译器检查而不是通过表达式检查肯定会很好。 :/
虽然看不到任何方法...
您可以通过以下方式引发异常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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语句检查状态比处理异常要高效得多。如果确实是例外情况,则一定要扔掉……但是要说,如果您无法打开设备,则最好还是坚持使用返回码。