Подтвердить что ты не робот

Существует ли стандартный атрибут "никогда не возвращает" для функций С#?

У меня есть один метод, который выглядит так:

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);
}

Я боюсь, что пользовательские атрибуты не будут делать, потому что для моей цели мне понадобится сотрудничество с компилятором.

4b9b3361

Ответ 1

Нет. Я предлагаю вам изменить подпись вашей первой функции, чтобы вернуть исключение, а не бросать его, и оставить оператор throw во второй функции. Это будет держать компилятор счастливым, а пахнет еще хуже.

Ответ 2

Почему бы просто не изменить его на

int foo(int x, y)
{
    if (y == 0)
        throwException("Doh!");
    return x/y;
}

Это дает те же результаты выполнения, и компилятор не будет жаловаться.

Ответ 3

Ответ Бернхофа верен. Однако, если вы пытаетесь инкапсулировать большой кусок логики при создании своего исключения, то все, что вам нужно сделать, это изменить код:

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'.");
    }
}

Ответ 4

Не направляйте создание исключения в другую функцию (т.е. просто бросайте его напрямую), и компилятор не будет жаловаться. Передача функции типа "хелпер" для броска исключений - пустая трата времени, если фактически функция добавляет значение к процессу исключения.

Ответ 5

Ваша функция

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){}

Ответ 6

Бернхоф уже дал вам возможность избежать компилятора. Однако также помните, что трассировка стека будет отключена (и некоторые библиотеки журналов не будут обрабатывать методы util-classed-i-throw-exceptions-for-your-app), что затрудняет отладку вашего приложения.

Ответ 7

Вы можете указать "никогда не возвращается" с помощью дженериков, чтобы объявить, что функция возвращает "что-то":

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 # могут вывести его).
  • Эта идиома очень необычна в С#, поэтому многие люди ее не узнают. Возможно, вам придется добавить комментарий, говорящий о том, что вы делаете.

Как говорят другие ответы, вам, вероятно, лучше отказаться от исключения, а не бросить его.

Ответ 8

Удалите ключевое слово 'else', оно все равно избыточно, и оно будет работать;)

Ответ 9

Ну, это "довольно" реализация с низким уровнем усилий.

Рабочий класс:

/// <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) по пустым методам.