Можно ли использовать оператор ?? и бросить новое исключение ()?

У меня есть несколько следующих методов:

var result = command.ExecuteScalar() as Int32?;
if(result.HasValue)
{
   return result.Value;
}
else
{
   throw new Exception(); // just an example, in my code I throw my own exception
}

Хотел бы я использовать оператор ?? вот так:

return command.ExecuteScalar() as Int32? ?? throw new Exception();

но выдает ошибку компиляции.

Можно ли переписать мой код или есть только один способ сделать это?


person abatishchev    schedule 19.11.2009    source источник
comment
Я хотел бы иметь возможность return this as T ?? that as T ?? other as T ?? throw new NotSupportedException(); Вместо этого мне нужно использовать временную переменную, проверить ее на нуль и вернуть временную. Это просто немного уродливее.   -  person Tergiver    schedule 04.07.2011
comment
На Connect() 2016 есть презентация, показывающая эту функцию для грядущего C# 7.   -  person Thomas    schedule 25.11.2016
comment
Похоже, в C#7 вы получили то, что хотели. .com/2016/09/01/c-7-additions-throw-expressions   -  person Svek    schedule 16.02.2017


Ответы (5)


Для C# 7

В C# 7 throw становится выражением, поэтому можно использовать именно тот код, который описан в вопросе.

Для C# 6 и более ранних версий

Вы не можете сделать это напрямую в C# 6 и более ранних версиях — второй операнд ?? должно быть выражением, а не оператором throw.

Есть несколько альтернатив, если вы действительно просто пытаетесь найти краткий вариант:

Вы можете написать:

public static T ThrowException<T>()
{
    throw new Exception(); // Could pass this in
}

А потом:

return command.ExecuteScalar() as int? ?? ThrowException<int?>();

Я действительно не рекомендую вам это делать... это довольно ужасно и однообразно.

Как насчет метода расширения:

public static T ThrowIfNull(this T value)
{
    if (value == null)
    {
        throw new Exception(); // Use a better exception of course
    }
    return value;
}

Затем:

return (command.ExecuteScalar() as int?).ThrowIfNull();

Еще одна альтернатива (опять же метод расширения):

public static T? CastOrThrow<T>(this object x) 
    where T : struct
{
    T? ret = x as T?;
    if (ret == null)
    {
        throw new Exception(); // Again, get a better exception
    }
    return ret;
}

Позвонить с помощью:

return command.ExecuteScalar().CastOrThrow<int>();

Это несколько уродливо, потому что вы не можете указать int? в качестве аргумента типа...

person Jon Skeet    schedule 19.11.2009
comment
Я думаю, это потому, что ты ответил не как Тони. Во всяком случае, противопоставил его для вас. Здесь вы на правильном пути, но думаю, что есть более приятная, обобщенная техника, которую я добавлю в качестве своего собственного ответа (с риском отрицательного ответа) - person philsquared; 19.11.2009
comment
Джон, не могли бы вы использовать общие ограничения параметров для создания двух методов CastOrThrow<T>, одного для типов/структур значений и одного для ссылочных типов? Первый будет использовать T?, тогда как последний будет использовать T. - person Adam Maras; 19.11.2009
comment
@Adam: К сожалению, у вас не может быть двух методов, единственным отличием от их подписи является тип вывода и/или общие ограничения. - person LukeH; 19.11.2009
comment
Способ расширения! Просто великолепно ThrowIfNull +1 - person Alex Bagnolini; 19.11.2009
comment
Привет @JonSkeet, вы можете обновить этот ответ с намеком на (в C # 7) добавленную функцию выполнения того, что хочет OP: structuredsight.com/2016/09/01/c-7-additions-throw-expressions - person Mafii; 24.02.2017
comment
@Mafii: В этом случае подойдет, хотя я не собираюсь просматривать все свои 7-летние ответы, чтобы проверять их ... - person Jon Skeet; 24.02.2017

Как уже было сказано, вы не можете сделать это с ?? оператора (ну, не без некоторых искажений, которые, кажется, не соответствуют вашей цели сделать это чище).

Когда я вижу этот паттерн, я сразу же думаю о применении. Родом из мира C++, они довольно хорошо переносятся в C#, хотя большую часть времени, возможно, менее важны.

Идея состоит в том, что вы берете что-то в форме:

if( condition )
{
  throw Exception;
}

и преобразует его в:

Enforce<Exception>( condition );

(вы можете еще больше упростить, установив тип исключения по умолчанию).

Продолжая, вы можете написать набор методов в стиле Nunit для различных проверок условий, например;

Enforce<Exception>.NotNull( obj );
Enforce<Exception>.Equal( actual, expected );
Enforce<Exception>.NotEqual( actual, expected );

и т.п.

Или, что еще лучше, предоставив ожидаемую лямбу:

Enforce<Exception>( actual, expectation );

Что действительно здорово, так это то, что после того, как вы это сделали, вы можете вернуть параметр actual и применить inline:

return Enforce( command.ExecuteScalar() as Int32?, (o) => o.HasValue ).Value;

... и это, кажется, ближе всего к тому, что вам нужно.

Я сбил реализацию этого раньше. Есть пара небольших мелочей, например, как вы обычно создаете объект исключения, который принимает аргументы — некоторые варианты выбора (в то время я выбрал отражение, но передача фабрики в качестве дополнительного параметра может быть даже лучше). Но в целом все довольно просто и может действительно очистить много кода.

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

person philsquared    schedule 19.11.2009
comment
Есть ли причина не использовать здесь метод расширения? return (command.ExecuteScalar() as int?).Enforce(x => x.HasValue); читается немного лучше для меня ... хотя, возможно, в этот момент стоит изменить имя. Мне нравится идея использования предиката. - person Jon Skeet; 19.11.2009
comment
В основном потому, что, когда я впервые сделал это на C#, я использовал C#2 ;-) Изначально я не использовал лямбда-выражение для предиката по той же причине, но перейти к этому было несложно. Я думаю, что метод расширения можно заставить работать хорошо, но придется немного поиграть с этим. - person philsquared; 19.11.2009

Если вам просто нужно исключение, когда возвращаемое значение не является Int32, сделайте следующее:

return (int)command.ExecuteScalar();

Если вы хотите создать свое собственное исключение, я бы, вероятно, сделал что-то вроде этого:

int? result = command.ExecuteScalar() as int?;
if (result == null) throw new YourCustomException();
return result.Value;
person LukeH    schedule 19.11.2009
comment
Да, будет выброшено исключение. Но, возможно, недопустимое исключение приведения не является подходящим исключением для создания; возможно, должно быть исключение для конкретного приложения в случае, если эта команда не возвращает значение. - person Adam Maras; 19.11.2009
comment
@Adam: Я действительно не думаю, что это заслуживает отрицательного голоса! Пример кода в вопросе выдает простой Exception. InvalidCastException или NullReferenceException более уместны и информативны, чем просто Exception без дополнительных деталей. - person LukeH; 19.11.2009
comment
Позвольте мне перефразировать; возможно, что в случае, если команда не вернет значение, потребуется создать исключение для конкретного приложения (например, NoRecordsExistException) (да, я знаю, что плакат не упоминал об этом , но некоторые люди удаляют такие особенности из своих вопросов.) Для этого нужно было бы обернуть опубликованное вами утверждение в блок try/catch, что противоречит цели сжатия кода. - person Adam Maras; 19.11.2009
comment
@Adam: Это правда, но без дополнительной информации от OP я бы повторил, что мой код проще, чем код в вопросе, и генерирует исключения, которые более информативны/уместны. . Если это не то, что требует ОП, они должны уточнить в вопросе. - person LukeH; 19.11.2009
comment
Мне это нравится. Как насчет: попробуйте { return (int) command.ExecuteScalar(); } поймать { бросить новое исключение NotFoundException(); } - person Sedat Kapanoglu; 19.11.2009
comment
@ Люк: правда. Я помешан на шаблонах/практиках, поэтому всегда ищу идеальные решения. Я больше не могу изменить свой голос... но вы приводите убедительный аргумент. - person Adam Maras; 19.11.2009
comment
@ssg: я не очень люблю использовать try...catch...throw, если в этом нет крайней необходимости. Я обновил вопрос, чтобы показать, что я, вероятно, сделаю, если потребуется пользовательское исключение. - person LukeH; 19.11.2009
comment
@Фил: странно. Это позволяло мне сейчас, раньше не позволяло. Я получил сообщение об ошибке о том, что голосование слишком старое, и что я не могу его изменить, если ответ не будет изменен. В любом случае, я изменил его сейчас. - person Adam Maras; 19.11.2009
comment
Вы можете использовать Execute-Around для переназначения исключения (это будет включать в себя другой метод, целью которого было перезвонить вам, завернутый в блок try-catch, выбрасывая исключение по вашему выбору. Но если вы делаете это, вы можете в любом случае используйте мое предложение о принудительном исполнении :-) - person philsquared; 19.11.2009
comment
Всем привет. Я отредактировал свой пост. Как упомянул Адам, я написал Exception просто в качестве примера. Мой исходный код выдает мое собственное исключение FinanceResultNotFoundException :) - person abatishchev; 19.11.2009

Вы не сможете создать исключение справа от оператора объединения с нулевым значением. Причина этого в том, что правая часть оператора должна быть выражением, а не оператором.

Оператор объединения null работает следующим образом: если левое значение оператора равно null, вернуть его; в противном случае вернуть то, что находится справа от оператора. Ключевое слово throw не возвращает значение; следовательно, его нельзя использовать справа от оператора.

person Adam Maras    schedule 19.11.2009

Причина, по которой вы не можете:

return command.ExecuteScalar() as Int32? ?? throw new Exception();

Это потому, что исключение является оператором, а не выражением.

Если вы просто хотите немного сократить код, возможно, это:

var result = command.ExecuteScalar() as Int32?;
if(result.HasValue) return result;
throw new Exception();

Нет необходимости в другом.

person Josh Smeaton    schedule 19.11.2009
comment
Это будет работать только в том случае, если функция, в которой находится этот оператор возврата, возвращает объект. Любой другой тип возвращаемого значения приведет к ошибке компилятора, поскольку левый и правый типы выражений оператора объединения null являются разными типами. - person Adam Maras; 19.11.2009
comment
Я так и думал, отсюда и "возможно". Я удалил эту часть своего ответа. - person Josh Smeaton; 19.11.2009
comment
Я предпочитаю инвертировать проверку на значение и бросать, если оно не имеет значения. Звучит более логично. - person Dykam; 19.11.2009