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;

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

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