В книге C#12 In a Nutshell (стр. 259, глава «Перегрузка true и false») они предоставляют нам этот пример, чтобы проиллюстрировать, как перегружать операторы true и false.
SqlBoolean a = SqlBoolean.Null;
if (a)
Console.WriteLine ("True");
else if (!a)
Console.WriteLine ("False");
else
Console.WriteLine ("Null");
public struct SqlBoolean
{
public static bool operator true (SqlBoolean x)
{
return x.m_value == True.m_value;
}
public static bool operator false(SqlBoolean x)
{
return x.m_value == False.m_value;
}
public static SqlBoolean operator ! (SqlBoolean x)
{
if (x.m_value == Null.m_value) return Null;
if (x.m_value == False.m_value) return True;
return False;
}
public static readonly SqlBoolean Null = new SqlBoolean (0);
public static readonly SqlBoolean False = new SqlBoolean (1);
public static readonly SqlBoolean True = new SqlBoolean (2);
SqlBoolean (byte value) { m_value = value; }
byte m_value;
}
Я пытаюсь понять, когда когда-либо вызывается перегрузка false
, и, насколько я понимаю (и подтверждаю это игрой с отладчиком), кажется, что она никогда не вызывается. почему тогда перегрузка false
может быть полезна? Поскольку мы всегда используем только while (a)
или while (!a)
и т. д., разве перегрузки true
и !
не будет достаточно?
Понятно, вопрос был в основном для того, чтобы я понял, «что происходит под капотом», я понимаю, что большинство «подробных» вещей, о которых я читал, не будут широко использоваться.
Ложный оператор вступит в действие, когда возникнет короткое замыкание - с оператором &&
, когда, если первое выражение не удалось, мы можем игнорировать второе.
В такой ситуации будет вызван ложный оператор, см. пример:
// Need to define this operator in order to leverage short circuiting with &&,
// it will be analogical with ||
public static SqlBoolean operator &(SqlBoolean left, SqlBoolean right)
{
// Implement appropriately, this is just example
return True;
}
И далее тест:
var shortCircuitAnd = a && a;
В стандарте C# есть такое примечание:
Примечание. Хотя
true
иfalse
не используются в выражениях явно (и, следовательно, не включены в таблицу приоритетов в §12.4.2), они считаются операторами, поскольку вызываются в нескольких контекстах выражений: логических выражениях (§12.24) и выражениях. с использованием условных (§12.18) и условных логических операторов (§12.14). конечная заметка
Перейдя по этим ссылкам, вы обнаружите, что оператор false
используется в условных логических операторах:
Операция
x && y
оценивается какT.false(x) ? x : T.&(x, y)
, гдеT.false(x)
— это вызовoperator false
, объявленный вT
, аT.&(x, y)
— это вызов выбранногоoperator &
. Другими словами,x
сначала оценивается, аoperator false
вызывается для результата, чтобы определить, является лиx
определенно ложным. Тогда, еслиx
определенно ложно, результатом операции будет значение, ранее вычисленное дляx
. В противном случае оцениваетсяy
, и выбранныйoperator &
вызывается для значения, ранее вычисленного дляx
, и значения, вычисленного дляy
, для получения результата операции.
Вот небольшой тестовый код, показывающий разное время использования операторов:
LoggingBoolean t = new LoggingBoolean(true);
LoggingBoolean f = new LoggingBoolean(false);
Console.WriteLine("Boolean expression");
if (t)
{
Console.WriteLine("In true body");
}
if (f)
{
Console.WriteLine("In false body");
}
Console.WriteLine("Conditional operator");
Console.WriteLine(t ? "true branch" : "false branch");
Console.WriteLine(f ? "true branch" : "false branch");
Console.WriteLine("Conditional && logic");
Console.WriteLine(t && t);
Console.WriteLine(t && f);
Console.WriteLine(f && t);
Console.WriteLine(f && f);
Console.WriteLine("Conditional || logic");
Console.WriteLine(t || t);
Console.WriteLine(t || f);
Console.WriteLine(f || t);
Console.WriteLine(f || f);
internal struct LoggingBoolean
{
public bool Value { get; }
public LoggingBoolean(bool value) => Value = value;
public static bool operator false(LoggingBoolean x)
{
Console.WriteLine("false operator called");
return !x.Value;
}
public static bool operator true(LoggingBoolean x)
{
Console.WriteLine("true operator called");
return x.Value;
}
public static LoggingBoolean operator &(LoggingBoolean x, LoggingBoolean y) =>
new LoggingBoolean(x.Value && y.Value);
public static LoggingBoolean operator |(LoggingBoolean x, LoggingBoolean y) =>
new LoggingBoolean(x.Value || y.Value);
public override string ToString() => $"LoggingBoolean({Value})";
}
Выход:
Boolean expression
true operator called
In true body
true operator called
Conditional operator
true operator called
true branch
true operator called
false branch
Conditional && logic
false operator called
LoggingBoolean(True)
false operator called
LoggingBoolean(False)
false operator called
LoggingBoolean(False)
false operator called
LoggingBoolean(False)
Conditional || logic
true operator called
LoggingBoolean(True)
true operator called
LoggingBoolean(True)
true operator called
LoggingBoolean(True)
true operator called
LoggingBoolean(False)
Кроме того, обратите внимание, что основной причиной реализации перегрузки операторов true и false действительно была обработка логики с тремя состояниями (например, логические значения SQL, которые могут иметь значение NULL). Теперь это лучше представлено с помощью типа bool, допускающего значение NULL, что делает перегрузка операторов true/false в наши дни крайне необычна.