检查私有字段与捕获异常

Checking a private field vs catching an exception

我有一个来自第三方程序集的 class(所以我无法编辑它):

public class MyClass
{
  private bool _loggedIn;
  public void Login() {_loggedIn = true;}
  public void Logout() {
    if (!_loggedIn) throw new InvalidOperationException();
    _loggedIn = false;
   }
}

现在,假设我有一个 MyClass 的实例(我不知道 _loggedIn),我需要调用 LogOut。以下哪种避免致命异常的方法通常会更快? (任何其他方法也可以):

首先,如果您的 class 没有为 LoggedIn 公开至少一个只读 属性,这听起来像是一个相当大的设计缺陷。

对于速度,使用反射通常会更快,特别是如果您缓存 FieldInfo 或使用 System.Linq.Expressions 构建 Func<bool>。这是因为异常在抛出时会收集大量调试信息,包括 StackTrace,这可能很昂贵。

不过,与任何事情一样,通常最好测试此类操作,因为有时会有优化或其他因素可能会让您感到惊讶。

这取决于您希望在应用程序中看到的不变量。

1. 如果您希望有很多 MyClass 具有不同的状态(登录、注销),那么最好避免异常开销(因为异常是 Exceptional 情况)并使用一些特定的 public IsLoggedIn 属性 (显然是为了避免反射)或一些 TryXxxxx-像方法。

即使您不能修改原始代码,也没有人会阻止您包装它:

public class MyWrappedClass
{
    public Boolean IsLoggedIn {get; private set;}
    private MyClass m_Log;

    public MyWrappedClass ()
    {
        this.m_Log = new MyClass();
        this.IsLoggedIn = false;
    }

    public void Log()
    {
          try
          {
              this.m_Log.LogIn();
              this.IsLoggedIn = true;
          }
          catch
          {
              this.IsLoggedIn = false;
          }
    }

    public void LogOut()
    {
        try
        {
            this.m_Log.LogOut();
            this.IsLoggedIn = false;
        }
        catch
        {
            this.IsLoggedIn = true;
        }
    }
}

您甚至可以进一步实施 IDisposable 接口以避免手动登录-注销管理:

public class MyWrappedClass
{
    private class LogSessionToken : IDisposable
    {
        private MyWrappedClass parent;
        public LogSessionToken (MyWrappedClass parent)
        {
            parent.LogIn();
        }

        public void Dispose()
        {
            parent.LogOut();
        }
    }

    public IDisposable LogSession()
    {
        return new LogSessionToken (this);
    }
     // ...
}

并像

一样使用它
using (var logToken = wrappedInstance.LogSession)
{
    // do the work.
} // No need to worry about manual LogOut

2. 如果您希望以适当的方式仅使用 MyClass 中的少数,那么最好根本不处理异常 - 如果发生错误然后是一些编程错误因此程序将被终止。

如果模式 if (CanFoo) Foo(); 出现的次数非常多,这往往非常强烈地暗示:

  1. 正确编写的客户端会知道何时可以或不可以调用 Foo。客户不知道的事实表明它可能在其他方面存在缺陷。

  2. class 公开 CanFooFoo 还应该公开一个方法,如果可能和适当的话,该方法将 Foo (该方法应该抛出如果无法建立预期的 post 条件,但如果 post 条件在调用之前建立,则应该 return 静默)

在 class 不控制的情况下应该提供这样的方法但没有提供,最干净的方法可能是编写自己的包装方法,其语义反映了缺失方法应该具有的语义。如果 class 的更高版本实现了缺失的方法,则更改代码以使用该实现可能比重构大量 if (CanFoo) 结构更容易。

顺便说一句,我建议设计得当的 class 应该允许调用代码指示它是否期望从登录状态转换到注销状态,或者它是否想要结束处于注销状态,但它不关心它是如何到达那里的。两种语义都有完全合法的用途;在第一种情况下,如果在关闭的会话上调用 LogOut 方法会抛出异常是一件好事,但在客户端代码只想确保它已注销的情况下,具有EnsureLoggedOut 可以无条件调用的方法比必须为此目的添加额外的客户端代码更简洁。