У меня есть класс из сторонней сборки (поэтому я не могу ее редактировать):
public class MyClass
{
private bool _loggedIn;
public void Login() {_loggedIn = true;}
public void Logout() {
if (!_loggedIn) throw new InvalidOperationException();
_loggedIn = false;
}
}
Теперь предположим, что у меня есть экземпляр MyClass
(для которого я не знаю _loggedIn
), и мне нужно вызвать LogOut
. Какой из следующих способов избежать фатального исключения, как правило, будет быстрее? (любой другой метод тоже будет хорош):
LogOut
, и если _loggedIn == false
, просто поймайте исключение_loggedIn == true
, и только вызовите LogOut
если это так Во-первых, если ваш класс не раскрывает, по крайней мере, свойство только для LoggedIn
для LoggedIn
, это похоже на довольно большой недостаток дизайна.
Для скорости использование отражения будет, как правило, более быстрым, особенно если вы FieldInfo
или FieldInfo
Func<bool>
используя System.Linq.Expressions
. Это связано с тем, что Исключения собирают много отладочной информации при броске, включая StackTrace
, что может быть дорогостоящим.
Как и в случае с чем-либо, часто лучше всего проверять такие операции, поскольку есть некоторая оптимизация или другие факторы, которые могут вас удивить.
Это зависит от инвариантов, которые вы ожидаете увидеть в своем приложении.
1. Если вы ожидаете, что у MyClass
будет много разных состояний (вход в систему, выйдите из системы), лучше избегать накладных расходов на исключение (поскольку исключение - это Исключительная ситуация) и использовать какое-то определенное свойство IsLoggedIn
(очевидно, чтобы избежать Reflection ) или некоторые TryXxxxx
-like.
И даже если вы не можете изменить исходный код, никто не останавливает вас от его упаковки:
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- интерфейс, чтобы избежать ручного управления LogIn-LogOut:
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();
появляется очень много, что имеет тенденцию очень сильно подразумевать, что либо:
Правильно написанный клиент знал бы, когда он может или не может вызвать Foo
. Тот факт, что клиент не знает, указывает на то, что он, вероятно, имеет недостатки другими способами.
Класс, CanFoo
и Foo
должен также выставлять метод, который будет Foo
если это возможно и целесообразно (метод должен бросать, если не удается установить ожидаемые пост-условия, но должен вернуться молча, если пост-условия были установлены до вызова)
В тех случаях, когда класс один не контролирует, должен предоставлять такой метод, но нет, самый чистый подход может заключаться в написании одного собственного метода обертки, семантика которого отражает те недостающие методы, которые должны были иметь. Если более поздняя версия класса реализует отсутствующий метод, изменение одного кода для использования этой реализации может быть проще, чем рефакторинг многих конструкций if (CanFoo)
.
BTW, я бы предположил, что правильно спроектированный класс должен позволить вызывающему коду указывать, ожидает ли он перехода из состояния входа в систему в состояние выключения или хочет ли он выйти из состояния выхода из системы, но он не " Не волнуйся, как это происходит. Оба вида семантики имеют совершенно законное применение; в случаях, когда первый вид был бы уместным, использование метода LogOut
исключение, если вызов на закрытом сеансе был бы хорошим, но в тех случаях, когда клиентский код просто хочет убедиться, что он вышел из системы, имея метод EnsureLoggedOut
который может быть вызвано безоговорочно, было бы более чистым, чем добавление дополнительного клиентского кода для этой цели.
LoggedIn
, но если бы у меня был выбор, я бы поймал исключение.Property
вы можете получить доступ.