C# 要么返回 false 要么啥都不做

Posted

技术标签:

【中文标题】C# 要么返回 false 要么啥都不做【英文标题】:C# either return false or do nothingC# 要么返回 false 要么什么都不做 【发布时间】:2011-06-15 04:06:13 【问题描述】:

我想知道是否有办法不必重复相同的 if 构造,而是调用 StatusCheck()。成功时不能返回true。 有人知道这个问题的更好标题吗?

bool Enable()

    if (!GetStatus(ref status))  Trace.WriteLine("Error"); return false; 
    // do stuff

    if (!GetStatus(ref status))  Trace.WriteLine("Error"); return false; 
    // do more stuff

    if (!GetStatus(ref status))  Trace.WriteLine("Error"); return false; 
    // do even more stuff

    // 6 more times the above

    return true;

【问题讨论】:

真实代码有6次相同的行? 我希望这并不代表实际代码。它调用同一个方法 9 次是有原因的吗? 真正的代码执行了许多方法,这些方法都依赖于前一个成功的方法。这些方法中的每一种都会更新状态。正在执行的方法不是我的代码。 @stijn 为什么是ref status 不是我的选择,这就是我正在使用的 SDK 的工作方式。我必须调用一个方法,获取状态,根据新的状态我可以调用下一个方法或者我可以停止 Enable() 函数。 【参考方案1】:

您可以创建一个CheckStatus() 方法,如果状态无效则抛出异常,然后在您的Enable() 方法中处理该异常:

public void CheckStatus(int status)

    if (!IsValidStatus(status)) 
        throw new InvalidStatusException(status);
    


public bool Enable()

    try 
        CheckStatus(status);
        // do stuff

        CheckStatus(status);
        // do more stuff

        CheckStatus(status);
        // do even more stuff

        // 6 more times the above

        return true;

     catch (InvalidStatusException) 
        Trace.WriteLine("Error");
        return false;
    

【讨论】:

好点。我想我倾向于将这种方法与我的建议结合起来,将“做事”/“做更多的事”代码重构到子程序中。 原则上你永远不应该在正常的程序流程中抛出异常,它可以工作,但它会降低性能并且一些调试可能会更棘手。 @David,好吧,这取决于。获得无效状态真的是“正常程序流程的一部分”吗? :) @Frédéric Hamidi: :) 当然,对于这种特殊情况,调试器噪音并不重要。但是,如果他将这种模式散布在各处,这可能会变得巨大(很有可能,因为这就是他的 SDK 的结构方式)。然后我们可能会在正常运行期间获得 100 多个首次机会异常。解决方案可能是使用设计更好的 SDK:P 代码甚至没有像原始 GetStatus 失败那样多次生成错误消息。原始代码会为每个 FAIL 显示一次消息,而新代码只显示一次。我可以否决两次吗?不,我不能。【参考方案2】:

我会将由“Do stuff”cmets 表示的代码重构为方法。您调用这些方法并捕获异常或检查这些方法的返回值(可能是当前状态),而不是重复调用GetStatus()

另外,我不明白为什么一个名为GetStatus() 的方法会有一个ref 参数,该参数似乎是由具有当前状态的方法更新的?如果您必须使用 GetStatus() 方法,我会让 GetStatus() 不带任何参数,而是实际返回当前状态。

Status status = GetStatus();

如果您选择允许这些方法抛出异常,请注意不要在抛出这些异常时开始应用真正的逻辑 - exceptions are not the right way to control program flow。

【讨论】:

我认为@Stijn 每次都调用 GetStatus() 是因为状态可能已经改变,所以他想尽快退出 Enable() 方法 @Axarydax 是的,我也这么认为。这就是为什么我建议从那些方法返回值来指示由于调用该方法而导致的状态,而不是仅仅重复点击GetStatus() 方法(特别是因为它有一个似乎指示状态的ref 参数......) “做事”一点也不为过。它调用一个 SDK 方法(该方法又调用非托管代码),而 SDK 不在我的控制范围内。另请参阅我对问题的评论。 @Stijn Ahh 好的。在这种情况下,我会按照 Frédéric 的建议将这个东西包装在一个大的 try-catch 中,将 GetStatus() 更改为 ValidateStatus() 并让它在不安全的情况下抛出异常。 @Stijn 明确地说,如果您能够控制代码并且有很多实际代码代替这些 cmets,我会将我的建议与 Frédéric 的建议结合起来。 【参考方案3】:

根据你想要的认真程度,你可以构建某种动作队列,以打破方法中的“do-stuff”。

protected delegate void DoStuffDelegate(ref TYPE statusToCheck);

bool Enable()

  List<DoStuffDelegate> stuffToDo = new ...
  stuffToDo.Add(myFirstMethod);
  stuffToDo.Add(mySecondMethod);
  foreach(var myDelegate in stuffToDo)
  
    if(!GetStatus(ref status))  Trace.WriteLine("Error"); return false; 
    myDelegate(ref status);
  

无论好坏,C# 都不允许任何其他构造(例如预处理器定义或我们在 C++ 中得到的构造)。

【讨论】:

这看起来像一个选项,我会试试看。 委托,委托列表,迭代 - 而不是普通方法。这很酷,但要复杂得多。 他们说我总是把事情复杂化:) GL 我已经接受了 Frédéric 的回答,以便获得更易读/更清晰的代码(对我自己和其他开发人员而言),但看起来这当然也可以。谢谢。【参考方案4】:

这个怎么样?它需要对现有代码进行最少的更改。 yield return是你的朋友;让编译器做所有的脏活:

IEnumerable<bool> StatusChecks()

    // Do stuff
    yield return GetStatus( ref status );
    // Do stuff
    yield return GetStatus( ref status );
    // Do stuff
    yield return GetStatus( ref status );


bool Enable()

    foreach ( var b in StatusChecks() )
    
        if ( !b )
           
            Trace.WriteLine("Error");                       
            return false;
         
    
    return true;

或者,如果您不介意带有副作用的 LINQ 查询,只需:

bool Enable()

     var result = StatusChecks().All( b => b );
     if ( !result )
     
         Trace.WriteLine("Error");                       
     
     return result;

【讨论】:

【参考方案5】:

你可以使用异常,让异常向上传播。

void GetStatusAndException(ref Status) 
    if (!GetStatus(ref status))) Throw new Exception("Status exception");

如果你不想要例外,有 对此您无能为力,除了将除 return 之外的所有内容都放入方法中:

bool GetStatusAndTrace(ref Status) 
    bool result = GetStatus(ref status))
    if (!result) Trace.WriteLine("Error");
    return result;


bool Enable()

    if (!GetStatusAndTrace(ref status))  return false; 
    // do stuff

    if (!GetStatusAndTrace(ref status))  return false; 
    // do more stuff

    if (!GetStatusAndTrace(ref status)) return false; 
    // do even more stuff

    // 6 more times the above

    return true;

【讨论】:

我认为有一个方法没有多大价值:1) 返回 true,2) 抛出(不过我认为 BCL 中有一些情况)。如果只有一个可能的结果,为什么要返回一个值? 如果要生成异常,返回值需要什么?只需调用该方法并假设它返回时一切正常。 这让我想起了我正在处理的代码中清除的“成语”:if(bad) throw new Exception(); return false; else return true; 是的,复制粘贴了太多的原始内容...已修复【参考方案6】:

一种可能的解决方案是为您正在使用的 API 创建一个“健全的”包装器:

class Wrapper

    public void DoStuff() // Add static modifiers, parameters and return values as necessary
    
        API.DoStuff();
        CheckStatus();
    

    public void DoSomeOtherStuff()
    
        API.DoSomeOtherStuff();
        CheckStatus();
    

    private void CheckStatus()
    
        Status status = default(Status);
        if(!GetStatus(ref status)) throw new InvalidStatusException();
    

一点点工作,但它可以让您的客户端代码更具可读性:

bool Enable()

    try
    
        Wrapper.DoStuff();
        Wrapper.DoSomeMoreStuff();
        return true;
    
    catch(InvalidStatusException)
    
        Trace.WriteLine("Error");
        return false;
    

【讨论】:

【参考方案7】:

现在您不需要使用异常,而且它仍然很容易阅读。不管怎样,你还是得打电话。异常花费大约 4000-8000 个时钟周期。

bool Enable()

    if (GetStatus(ref status)) 
     
        // do stuff
        if (GetStatus(ref status)) 
         
            // do stuff
            if (GetStatus(ref status)) 
             
                // do stuff
                return true;
            
        
    

    Trace.WriteLine("Error"); 
    return false;

【讨论】:

【参考方案8】:

使用 LINQ,您可以执行以下操作:

delegate bool StatusFunc( ref int status );

bool Enable()

    var funcs = new StatusFunc[] 
    
        GetStatusA,
        GetStatusB,
        GetStatusC
    ;
    return funcs.All( f => f( ref status ) );

请注意,这有点小技巧,因为GetStatus 函数有副作用(修改状态),这是 LINQ 查询中的禁忌。

【讨论】:

【参考方案9】:

将代码包装在 try-catch 块中,并重写您的 GetStatus() 以在错误时引发异常。如果你想按书去做,实现你自己的继承自 System.Exception 的异常。然后,如果您稍后需要该信息,您还可以将“状态”属性添加到异常中(但是您的代码示例并未如此说明)。

类似这样的:

bool Enable()

    try
    
        GetStatus(ref status);
        // do stuff

        GetStatus(ref status);
        // do more stuff

        GetStatus(ref status);
        // do even more stuff

        // 6 more times the above
        return true;
    
    catch (Exception ex)
    
        Trace.WriteLine("Error"); 
        return false;
    

就像我说的,抛出和捕获的异常应该是一个自定义异常,但是上面的会起作用。

【讨论】:

【参考方案10】:

让我们看看我是否理解正确。

您可以使用规范设计模式: http://en.wikipedia.org/wiki/Specification_pattern

您应该使用类似上述设计模式的实现,而不是使用硬编码验证,因为它为您提供了一种针对对象状态添加规则的更可扩展的方式。

例如,您将定义一个规范“StatusSpecification”,该规范“用于验证状态是否正确”。结果可能是布尔值。

违反规则的评估器会采用该布尔结果并做出所需的决定(抛出异常?尝试恢复?结束应用程序?只是通知用户有关错误?报告错误?)。

总结:您需要有一些“规范加载器”并针对对象类型的加载规则执行对象状态。最后,在这种情况下,您将在一行中评估该状态(从消费者的角度来看),并且布尔结果可能是您的属性标志。

【讨论】:

很好,如果您有任何问题,请在此处发表评论。明天我会很AFK,但我会尝试阅读你的cmets。 这是一个典型的例子,说明大多数设计模式如何被函数式编程淘汰(整篇文章基本上只是描述了一个谓词函数 我相信你的说法不正确。我已经讨论了规范,而不是关于如何实现它。我有几个项目使用 lambda 函数来定义规范。我想让他从抽象的角度理解解决问题的方法。顺便说一句,您可以使用类型层次结构来实现规范,但是您可以使用 lambda 函数来定义它们,这是规范的“验证”方法。有很多选择! :)【参考方案11】:
bool Enable()

    try 
    if (!newProc 
    // do stuff

    if (!newProc 
    // do more stuff

    if (!newProc 
    // do even more stuff

   // 6 more times the above

   return true;
   
   catch 
       return false;
   


public function newProc()

    if (!GetStatus(ref status))  Trace.WriteLine("Error"); return false; 

    throw new Exception

【讨论】:

以上是关于C# 要么返回 false 要么啥都不做的主要内容,如果未能解决你的问题,请参考以下文章

返回executeFetchRequest的NSArray:结果时我应该保留,自动释放还是啥都不做?

python中合适的“啥都不做” lambda表达式?

在 C# IIS 中调用 bat 文件啥都不做

数据库事务详解

拯救错误啥都不做?

UIViewController presentModalViewController:动画:啥都不做?