How-to: короткозамкнутый инвертированный тернарный оператор, реализованный в, например, С#? Это имеет значение?
Предположим, что вы используете тернарный оператор или оператор нулевого коалесцирования или вложенные операторы if-else для выбора назначения объекту. Теперь предположим, что в условном выражении вы оцениваете дорогостоящую или неустойчивую операцию, требуя, чтобы вы поместили результат во временную переменную, захватив ее состояние, чтобы ее можно было сравнить, а затем потенциально присвоить.
Как бы язык, например С#, для рассмотрения, реализовать новый логический оператор для обработки этого случая? Должно ли это? Существуют ли существующие способы обработки этого случая в С#? Другие языки?
Некоторые случаи сокращения многословия тройного или нулевого коалесцирующего оператора были преодолены, если предположить, что мы ищем прямые сравнения. См. Уникальные способы использования оператора Null Coalescing, в частности обсуждение того, как можно расширить использование оператора для поддержки String.IsNullOrEmpty(string)
. Обратите внимание, что Jon Skeet использует PartialComparer
из MiscUtil
, чтобы переформатировать 0
в null
s,
Почему это возможно? Посмотрите, как мы пишем метод сравнения для сложных объектов без каких-либо ярлыков (примеры из приведенных обсуждений):
public static int Compare( Person p1, Person p2 )
{
return ( (result = Compare( p1.Age, p2.Age )) != 0 ) ? result
: ( (result = Compare( p1.Name, p2.Name )) != 0 ) ? result
: Compare( p1.Salary, p2.Salary );
}
Джон Скит пишет новое сравнение, чтобы отменить случай равенства. Это позволяет выразить выражение путем записи нового конкретного метода, который возвращает null, что позволяет нам использовать нулевой оператор коалесцирования:
return PartialComparer.Compare(p1.Age, p2.Age)
?? PartialComparer.Compare(p1.Name, p2.Name)
?? PartialComparer.Compare(p1.Salary, p2.Salary)
?? 0;
Оператор нулевого коалесцирования более читабельен, поскольку имеет две стороны, а не три. Условие boolean condition разделяется на метод, в этом случае возвращающий null
, если выражение должно быть продолжено.
Как бы выглядело выражение выше, если бы мы могли легко поставить условие в строку? Возьмите выражение из PartialComparer.Compare
, которое возвращает null
, и поместите его в новое тернарное выражение, которое позволяет нам использовать оценку левого выражения с неявной временной переменной value
:
return Compare( p1.Age, p2.Age ) unless value == 0
: Compare( p1.Name, p2.Name ) unless value == 0
: Compare( p1.Salary, p2.Salary );
Основным "потоком" выражения будет:
выражение A, если только boolean B, и в этом случае выражение C
Вместо того, чтобы быть перегруженным оператором сравнения, я полагаю, что это больше похоже на короткозамкнутый инвертированный тернарный оператор.
- Будет ли этот тип логики полезен? В настоящее время нулевое коалесцирование дает нам способ сделать это с условным выражением
(value == null)
.
- Какие еще выражения вы бы хотели протестировать? Мы слышали о
(String.IsNullOrEmpty(value))
.
- Каким будет лучший способ выразить это на языке, с точки зрения операторов, ключевых слов?
Ответы
Ответ 1
лично я бы избегал короткого замыкания от операторов и просто позволял методам его цепью:
public static int CompareChain<T>(this int previous, T a, T b)
{
if (previous != 0)
return previous;
return Comparer<T>.Default.Compare(a,b);
}
используйте так:
int a = 0, b = 2;
string x = "foo", y = "bar";
return a.Compare(b).CompareChain(x,y);
может быть встроен в JIT, поэтому он может выполнять так же хорошо, как и короткое замыкание, встроенное в язык, без лишних сложностей.
В ответ на вопрос, может ли вышеуказанная "структура" применяться не только к сравнению, но и да, она может, сделав выбор, продолжать или не выявлять и контролировать пользователя. Это по своей сути более сложное, но операция более гибкая, поэтому это неизбежно.
public static T ElseIf<T>(
this T previous,
Func<T,bool> isOK
Func<T> candidate)
{
if (previous != null && isOK(previous))
return previous;
return candidate();
}
то используйте так:
Connection bestConnection = server1.GetConnection()
.ElseIf(IsOk, server2.GetConnection)
.ElseIf(IsOk, server3.GetConnection)
.ElseIf(IsOk, () => null);
Это максимальная гибкость в том, что вы можете изменить проверку IsOk на любом этапе и полностью ленивы. В ситуациях, когда проверка в порядке, одинакова в каждом случае, вы можете упростить подобное и полностью избегать методов расширений.
public static T ElseIf<T>(
Func<T,bool> isOK
IEnumerable<Func<T>[] candidates)
{
foreach (var candidate in candidates)
{
var t = candidate();
if (isOK(t))
return t;
}
throw new ArgumentException("none were acceptable");
}
Вы можете сделать это с помощью linq, но этот способ дает хорошее сообщение об ошибке и позволяет это
public static T ElseIf<T>(
Func<T,bool> isOK
params Func<T>[] candidates)
{
return ElseIf<T>(isOK, (IEnumerable<Func<T>>)candidates);
}
что приводит к хорошему считываемому коду:
var bestConnection = ElseIf(IsOk,
server1.GetConnection,
server2.GetConnection,
server3.GetConnection);
Если вы хотите разрешить значение по умолчанию, то:
public static T ElseIfOrDefault<T>(
Func<T,bool> isOK
IEnumerable<Func<T>>[] candidates)
{
foreach (var candidate in candidates)
{
var t = candidate();
if (isOK(t))
return t;
}
return default(T);
}
Очевидно, что все вышеизложенное может быть легко написано с использованием lambdas, поэтому ваш конкретный пример:
var bestConnection = ElseIfOrDefault(
c => c != null && !(c.IsBusy || c.IsFull),
server1.GetConnection,
server2.GetConnection,
server3.GetConnection);
Ответ 2
Чтобы помещать одну предложенную реализацию в сторону из очень подробного вопроса, пусть работает с ключевым словом unless
.
(выражение A) unless
(boolean B) < magical ", в этом случае" operator > (выражение C)
... было бы все, что есть.
Логическое выражение B будет иметь доступ к оценке выражения A через ключевое слово value
. Выражение C может иметь ключевое слово unless
в своем выражении, что позволяет простое, линейное объединение.
Кандидаты на "магический", в этом случае "оператор":
-
:
-
|
-
?:
-
otherwise
ключевое слово
Ответ 3
Использование любых символов, как правило, уменьшает читаемость для среднего разработчика. Даже оператор ??
не используется широко. Я сам предпочитаю разрабатывать подробный код, но я могу легко читать через год.
Итак, кандидат для вашего:
выражение A, если только boolean B, в этом случае выражение C.
будет
выражение A, если только boolean B sothen выражение C.
Хотя многие люди вроде меня все равно будут использовать:
if (B) {expression C;}
else {expression A;}
Это происходит, когда вы разрабатываете программное обеспечение с большой командой, с разным фоном, каждый из которых находится в команде мастера одного языка и просто пользователь других.
Ответ 4
Подробнее @ShuggyCoUk: Ах, я вижу, что это может работать не только для сравнения? Я не использовал С# 3 и методы расширения, но я полагаю, что вы можете объявить для моего предыдущего примера ниже
public delegate bool Validation<T>( T toTest );
public static T Validate<T>( this T leftside, Validation<T> validator )
{
return validator(leftside) ? leftside : null;
}
Далее: за Скит:
Validation<Connection> v = ( Connection c ) => ( c != null && !( c.IsBusy || c. IsFull ) );
Connection bestConnection =
server1.GetConnection().Validate( v ) ??
server2.GetConnection().Validate( v ) ??
server3.GetConnection().Validate( v ) ?? null;
Как это работает на С#? Комментарии оценены. Спасибо.
В ответ на ShuggyCoUk:
Итак, это метод расширения в С# 3, то? Кроме того, результатом здесь является int, а не произвольное выражение. Полезно для перегрузки еще одного метода сравнения. Предположим, мне нужно выражение для выбора наилучшего соединения. В идеале я хочу что-то упростить:
Connection temp;
Connection bestConnection =
( temp = server1.GetConnection() ) != null && !(temp.IsBusy || temp.IsFull) ? temp
: ( temp = server2.GetConnection() ) != null && !(temp.IsBusy || temp.IsFull ) ? temp
: ( temp = server3.GetConnection() ) != null && !(temp.IsBusy || temp.IsFull ) ? temp
: null;
Хорошо, поэтому можно было бы иметь метод
bool IsOk( Connection c )
{
return ( c != null && !(c.IsBusy || c.IsFull) );
}
Что создаст:
Connection temp;
Connection bestConnection =
( temp = server1.GetConnection() ) && IsOk( temp ) ? temp
: ( temp = server2.GetConnection() ) && IsOk( temp ) ? temp
: ( temp = server3.GetConnection() ) && IsOk( temp ) ? temp
: null;
Но как будет работать цепочка методов для сравнения? Я размышляю над тем, что выглядит следующим образом:
Connection bestConnection =
server1.GetConnection() unless !IsOk(value) otherwise
server2.GetConnection() unless !IsOk(value) otherwise
server3.GetConnection() unless !IsOk(value) otherwise null;
Я думаю, что есть до сих пор, обручи, чтобы проскочить, если я хочу, чтобы результат условного выражения был выражением или результатом метода, который был в исходном условном выражении.
Я предполагаю, что объект, возвращаемый такими методами, будет дорогим для создания или изменится при следующем вызове метода.