Ответ 1
Нет. Я предлагаю вам изменить подпись вашей первой функции, чтобы вернуть исключение, а не бросать его, и оставить оператор throw во второй функции. Это будет держать компилятор счастливым, а пахнет еще хуже.
У меня есть один метод, который выглядит так:
void throwException(string msg)
{
throw new MyException(msg);
}
Теперь, если я пишу
int foo(int x, y)
{
if (y == 0)
throwException("Doh!");
else
return x/y;
}
компилятор будет жаловаться на foo, что "не все пути возвращают значение".
Есть ли атрибут, который я могу добавить для throwException, чтобы этого избежать? Что-то вроде:
[NeverReturns]
void throwException(string msg)
{
throw new MyException(msg);
}
Я боюсь, что пользовательские атрибуты не будут делать, потому что для моей цели мне понадобится сотрудничество с компилятором.
Нет. Я предлагаю вам изменить подпись вашей первой функции, чтобы вернуть исключение, а не бросать его, и оставить оператор throw во второй функции. Это будет держать компилятор счастливым, а пахнет еще хуже.
Почему бы просто не изменить его на
int foo(int x, y)
{
if (y == 0)
throwException("Doh!");
return x/y;
}
Это дает те же результаты выполнения, и компилятор не будет жаловаться.
Ответ Бернхофа верен. Однако, если вы пытаетесь инкапсулировать большой кусок логики при создании своего исключения, то все, что вам нужно сделать, это изменить код:
void throwException(string msg) {
throw new MyException(msg);
}
:
Exception makeException(string msg) {
return new MyException(msg);
}
Затем ваш код вызова будет выглядеть следующим образом:
int foo(int x, y) {
if (y == 0) {
throw makeException("Doh!");
}
return x / y;
}
При прочих равных условиях предпочитайте функциональный код процессуальному коду. Это проще для повторного использования и модульного тестирования.
EDIT:
В свете кода примера Фреда это то, что я сделал бы. Это не кодовый контракт, но он все еще функционирует.
private int getVarID(string s_varID) {
int varID;
if(s_varID == "ILT") {
return 123;
} else if(s_varID == "TL") {
return 456;
} else if(s_varID == "FT") {
return 789;
} else if(int.TryParse(s_varID, out varID)) {
return varID;
} else {
throw makeParseError("varID must be an integer or 'ILT', 'TL' or 'FT'.");
}
}
Не направляйте создание исключения в другую функцию (т.е. просто бросайте его напрямую), и компилятор не будет жаловаться. Передача функции типа "хелпер" для броска исключений - пустая трата времени, если фактически функция добавляет значение к процессу исключения.
Ваша функция
void throwException(string msg)
{
throw new MyException(msg);
}
добавьте нулевое значение в код, поэтому ваш вопрос спорный. Если, с другой стороны, вы хотите выбросить ошибку с тем же сообщением по всему классу и свести к минимуму дублирование кода, это то, что вы должны делать.
Обычной практикой было бы расширить MyException
для этого конкретного случая и выбросить это:
public class HomerSimpsonException : MyException
{
public HomerSimpsonException() : base ("DOH!!!"){
}
}
int foo(int x, y)
{
if (y == 0)
throw new HomerSimpsonException();
else
return x/y;
}
Даже тогда, что недостаточно полно в соответствии с правилом Microsoft для расширения исключений, существует минимум 4 конструктора, которые вы должны реализовать - http://msdn.microsoft.com/en-us/library/ms182151%28VS.80%29.aspx, а именно:
public NewException(){}
public NewException(string){}
public NewException(string, Exception){}
protected or private NewException(SerializationInfo, StreamingContext){}
Бернхоф уже дал вам возможность избежать компилятора. Однако также помните, что трассировка стека будет отключена (и некоторые библиотеки журналов не будут обрабатывать методы util-classed-i-throw-exceptions-for-your-app), что затрудняет отладку вашего приложения.
Вы можете указать "никогда не возвращается" с помощью дженериков, чтобы объявить, что функция возвращает "что-то":
T ThrowException<T>(string msg)
{
throw new MyException(msg);
}
Итак, теперь вы можете написать:
int foo(int x, int y)
{
if (y == 0)
return ThrowException<int>("Doh!");
else
return x/y;
}
Эта идиома используется в таких языках, как Haskell и F #, и основан на принципе взрыва , также известном как "ex falso quodlibet". Обоснование таково: если функция никогда не возвращается, то мы можем сделать любые магические предположения, которые мы хотим о ее возвращаемом значении, поскольку такое значение никогда не будет существовать. Здесь вызывающий (foo) предполагает, что ThrowException вернет int.
Несколько незначительных недостатков:
ThrowException
может обойти это, возвращая default(T)
.ThrowException
(Haskell и F # могут вывести его).Как говорят другие ответы, вам, вероятно, лучше отказаться от исключения, а не бросить его.
Удалите ключевое слово 'else', оно все равно избыточно, и оно будет работать;)
Ну, это "довольно" реализация с низким уровнем усилий.
Рабочий класс:
/// <summary>
/// Representation of an unreachable type, exposing a method to represent unreachable code.
/// </summary>
public static class Unreachable {
/// <summary>
/// Representation of unreachable code with return semantics.
/// </summary>
public static dynamic Code() {
throw new NotImplementedException(@"Unreachable code was reached.");
}
}
Пример:
public object[] UnreachableCodeTest() {
return Unreachable.Code();
}
декомпилированные:
Offset OpCode Operand
0 ldsfld System.Runtime.CompilerServices.CallSite`1<System.Func`3<System.Runtime.CompilerServices.CallSite,System.Object,System.Object[]>> TestApp.Program/<UnreachableCodeTest>o__SiteContainere6::<>p__Sitee7
5 brtrue.s -> (10) ldsfld System.Runtime.CompilerServices.CallSite`1<System.Func`3<System.Runtime.CompilerServices.CallSite,System.Object,System.Object[]>> TestApp.Program/<UnreachableCodeTest>o__SiteContainere6::<>p__Sitee7
7 ldc.i4.0
8 ldtoken System.Object[]
13 call System.Type System.Type::GetTypeFromHandle(System.RuntimeTypeHandle)
18 ldtoken TestApp.Program
23 call System.Type System.Type::GetTypeFromHandle(System.RuntimeTypeHandle)
28 call System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder::Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags,System.Type,System.Type)
33 call System.Runtime.CompilerServices.CallSite`1<!0> System.Runtime.CompilerServices.CallSite`1<System.Func`3<System.Runtime.CompilerServices.CallSite,System.Object,System.Object[]>>::Create(System.Runtime.CompilerServices.CallSiteBinder)
38 stsfld System.Runtime.CompilerServices.CallSite`1<System.Func`3<System.Runtime.CompilerServices.CallSite,System.Object,System.Object[]>> TestApp.Program/<UnreachableCodeTest>o__SiteContainere6::<>p__Sitee7
43 ldsfld System.Runtime.CompilerServices.CallSite`1<System.Func`3<System.Runtime.CompilerServices.CallSite,System.Object,System.Object[]>> TestApp.Program/<UnreachableCodeTest>o__SiteContainere6::<>p__Sitee7
48 ldfld !0 System.Runtime.CompilerServices.CallSite`1<System.Func`3<System.Runtime.CompilerServices.CallSite,System.Object,System.Object[]>>::Target
53 ldsfld System.Runtime.CompilerServices.CallSite`1<System.Func`3<System.Runtime.CompilerServices.CallSite,System.Object,System.Object[]>> TestApp.Program/<UnreachableCodeTest>o__SiteContainere6::<>p__Sitee7
58 call System.Object TestApp.Unreachable::Code()
63 callvirt !2 System.Func`3<System.Runtime.CompilerServices.CallSite,System.Object,System.Object[]>::Invoke(!0,!1)
68 ret
Оптимизируйте, выполнив что-либо w/Fody, ищите эту сигнатуру вызова и замените ее на один необработанный ret (если это позволит вам) или какая-либо другая простая приемлемая недорогая альтернатива.
(Edit)
Как вы меня поменяете, я буду вынужден объяснить, почему это эффективно и полезно.
В основном это относится к блоку, который похож на конец метода, который имеет логически недостижимый путь;
#pragma warning disable 162
// ReSharper disable CSharpWarnings::CS0162
// ReSharper disable HeuristicUnreachableCode
return Unreachable.Code();
// ReSharper restore HeuristicUnreachableCode
// ReSharper restore CSharpWarnings::CS0162
#pragma warning restore 162
Если вы измените код выше этого раздела таким образом, чтобы код был достигнут, ваши тесты потерпят неудачу. Если у вас есть код ниже, вы ошибаетесь, и компилятор сообщит вам об этом. Как только вы перейдете за пределы первоначальной зрелости, вы удалите этот блок кода вместе. Основная цель этого - уловить сценарии, где код не может быть недоступен, когда он должен быть.
В других сценариях, где код не должен появляться, но не может быть логически исключен компилятором (например, в случае с примером по умолчанию), вы обычно бросаете ошибку. Если вы хотите оптимизировать этот сценарий, вам нужна такая реализация.
select( thisIsAlways123Never0OrGreaterThan3 ) {
default: return Unreachable.Code();
case 1: DoSomething(); break;
case 2: DoSomethingElse(); break;
case 3: return GetSomething();
}
Чтобы оптимизировать так, что минимальные инструкции испускаются для пути default:
с этим минимально написанным кодом, вам нужен друг с именем Fody.
Идеальный сценарий - результат, аналогичный тому, что хотят разработчики С++ в GCC и LLVM __builtin_unreachable()
или MSVC __assume(0)
или __declspec(noreturn)
по пустым методам.