Когда когда-либо выполняется перегруженный фальшивый оператор, и для чего он хорош?
Я искал фактический рабочий код, где фактически запускается перегруженный оператор false
.
Этот вопрос (Что такое фальшивый оператор в С# для?), является одним и тем же, но принятый ответ ссылается на URL-адрес, который возвращает ошибку 404. Я также посмотрел на Как происходит перегрузка оператора истиной и ложью? и некоторые другие вопросы.
То, что я нашел почти во всех ответах, заключается в том, что false
запускается только при использовании короткого замыкания и, как x && y
. Это оценивается как T.false(x) ? x : T.&(x, y)
.
Итак, у меня есть следующий код. struct
содержит int
и считает себя истинным, если int больше нуля.:
public struct MyStruct {
private int _i;
public MyStruct(int i) {
_i = i;
}
public static bool operator true(MyStruct ms) {
return ms._i > 0;
}
public static bool operator false(MyStruct ms) {
return ms._i <= 0;
}
public override string ToString() {
return this._i.ToString();
}
}
Теперь я надеюсь, что следующая программа выполнит и будет использовать перегруженный оператор false
.
class Program {
private static void Main() {
MyStruct b1 = new MyStruct(1); // to be considered true
MyStruct b2 = new MyStruct(-1); // to be considered false
Console.WriteLine(b1 && b2);
Console.WriteLine(b2 && b1);
}
}
Однако он даже не компилируется. В нем говорится, что он не может применять оператор '& &' к операндам типа "MyStruct" и "MyStruct".
Я знаю, что могу реализовать перегрузку оператора &
. Так что сделайте это. &
должен возвращать a MyStruct
, поэтому я не могу заставить его возвращать bool
.
public static MyStruct operator &(MyStruct lhs, MyStruct rhs) {
return new MyStruct(lhs._i & rhs._i);
}
Теперь код компилируется. Его выход - 1
и -1
. Таким образом, результат b1 && b2
не совпадает с результатом b2 && b1
.
Если я отлаживаю код, я вижу, что b1 && b2
сначала выполняет оператор false
на b1
, который возвращает false
. Затем он выполняет оператор &
на b1 и b2, который выполняет поразрядное и на 1 и -1, что приводит к 1. Поэтому он действительно проверяет, является ли b1 ложным.
Второе выражение b2 && b1
сначала выполняет оператор false
на b2
, который возвращает true
. В сочетании с тем, что я использую короткое замыкание, он ничего не делает с b1
и просто распечатывает значение b2
.
Итак, да, оператор false
выполняется, когда вы используете короткое замыкание. Однако он не выполняет оператор true
или false
во втором аргументе, но вместо этого выполняет перегруженный оператор &
в операндах.
Когда это когда-нибудь может быть полезно? Или как я могу сделать свой тип так, чтобы он мог проверить, истинны ли обе переменные?
Ответы
Ответ 1
ИЗМЕНИТЬ -
Считывая связанную статью, я смог получить следующий вывод, который использует как истинные, так и ложные операторы:
op false on 1
op & on 1 -1
op true on 1
op true on -1
FALSE
op false on -1
op true on -1
FALSE
op true on 1
op true on 1
TRUE
op true on -1
op & on -1 1
op true on -1
op true on 1
TRUE
С кодом:
class Program
{
static void Main(string[] args)
{
MyStruct b1 = new MyStruct(1); // to be considered true
MyStruct b2 = new MyStruct(-1); // to be considered false
Console.WriteLine((b1 && b2) ? "TRUE" : "FALSE");
Console.WriteLine((b2 && b1) ? "TRUE" : "FALSE");
Console.WriteLine((b1 || b2) ? "TRUE" : "FALSE");
Console.WriteLine((b2 || b1) ? "TRUE" : "FALSE");
Console.ReadLine();
}
}
public struct MyStruct
{
private int _i;
public MyStruct(int i)
{
_i = i;
}
public static bool operator true(MyStruct ms)
{
Console.WriteLine("op true on {0}", ms);
return ms._i > 0;
}
public static bool operator false(MyStruct ms)
{
Console.WriteLine("op false on {0}", ms);
return ms._i <= 0;
}
public static MyStruct operator &(MyStruct lhs, MyStruct rhs)
{
Console.WriteLine("op & on {0} {1}", lhs, rhs);
if (lhs)
{
return rhs;
}
else
{
return new MyStruct(-1); //-1 is false
}
}
public static MyStruct operator |(MyStruct lhs, MyStruct rhs)
{
Console.WriteLine("op & on {0} {1}", lhs, rhs);
if (lhs)
{
return lhs;
}
else
{
return rhs;
}
}
public override string ToString()
{
return this._i.ToString();
}
}
Я не уверен, что вы имеете в виду, когда говорите, что первый код не может скомпилировать, хотя он не использует оператор true/false, я запустил следующий код в экспресс-версии 2010 и получил вывод:
op bool on 1
op bool on -1
False
op bool on -1
False
op bool on -1
op bool on 1
True
op bool on 1
True
код:
class Program
{
static void Main(string[] args)
{
MyStruct b1 = new MyStruct(1); // to be considered true
MyStruct b2 = new MyStruct(-1); // to be considered false
Console.WriteLine(b1 && b2);
Console.WriteLine(b2 && b1);
Console.WriteLine(b2 || b1);
Console.WriteLine(b1 || b2);
Console.ReadLine();
}
}
public struct MyStruct
{
private int _i;
public MyStruct(int i)
{
_i = i;
}
public static bool operator true(MyStruct ms)
{
Console.WriteLine("op true on {0}", ms);
return ms._i > 0;
}
public static bool operator false(MyStruct ms)
{
Console.WriteLine("op false on {0}", ms);
return ms._i <= 0;
}
public static implicit operator bool(MyStruct ms)
{
Console.WriteLine("op bool on {0}", ms);
return ms._i > 0;
}
public override string ToString()
{
return this._i.ToString();
}
}
Ответ 2
Содержимое указанного вами URL-адреса 404 можно найти здесь:
http://web.archive.org/web/20080613013350/http://www.ayende.com/Blog/archive/2006/08/04/7381.aspx
Статья, на которую ссылается автор, находится здесь:
http://web.archive.org/web/20081120013852/http://steve.emxsoftware.com/NET/Overloading+the++and++operators
Чтобы избежать повторения этой проблемы, рассмотрим основные моменты статьи:
Несколько месяцев назад я опубликовал наш API запросов и объяснил, как он работает. Наш API запросов позволяет нам выражать наши запросы с использованием строго типизированного синтаксиса С#:
List<Customer> customers = repository.FindAll(Customer.Columns.Age == 20 & Customer.Columns.Name == "foo");
Одна из вещей, на которые я указывал в своих предыдущих сообщениях, заключалась в том, что я не мог перегружать && и || операторы, так как структура не допускает такого безумия... по крайней мере, не напрямую.
В частности, невозможно перегрузить доступ к члену, вызов метода, или =, &, ||,?:, checked, unchecked, new, typeof, as и является операторами. http://msdn.microsoft.com/library/default.asp?url=/library/en-us/csspec/html/vclrfcsharpspec_7_2_2.asp
В течение последнего месяца я провел небольшое исследование по этой теме, чтобы узнать, можно ли и как я могу получить && и || вести себя так, как я хочу. В этот вечер я наткнулся на страницу условных логических операторов в MSDN, которая дала мне ответ, который я искал:
Операция x && y оценивается как T.false(x)? x: T. & (x, y), где T.false(x) является вызовом оператора false, объявленного в T, и T. & (x, y) является вызовом выбранного оператора &. Другими словами, сначала оценивается x, а на результат вызывается оператор false, чтобы определить, является ли x определенно ложным. Тогда, если x определенно ложно, результатом операции является значение, ранее вычисленное для x. В противном случае y вычисляется, а выбранный оператор и вызывается на значение, ранее вычисленное для x, и значение, вычисленное для y, для получения результата операции. Операция x || y оценивается как T.true(x)? x: T. | (x, y), где T.true(x) является вызовом оператора true, объявленного в T, а T. | (x, y) является вызовом выбранного оператора |. Другими словами, сначала оценивается x, а на результат вызывается оператор true, чтобы определить, является ли x определенно истинным. Тогда, если x определенно верно, результатом операции является значение, ранее вычисленное для x. В противном случае y оценивается, а выбранный оператор | вызывается на значение, ранее вычисленное для x, и значение, вычисленное для y, чтобы получить результат операции. Поскольку у нас уже есть и и | операторы на месте, это было просто перегрузкой истинных и ложных операторов для возврата false. Это приводит к тому, что и и | операторы, которые всегда вызываются, что в свою очередь приводит к тому, что два объекта критериев превращаются в AndCriteria/OrCriteria!
Итак, теперь мы можем выразить наши выражения критериев, используя && и || синтаксис привыкли.
repository.FindAll(Customer.Columns.Age == 20 && Customer.Columns.Name == "foo");
repository.FindAll(Customer.Columns.FirstName == "Foo" || Customer.Columns.LastName == "Bar");
Соответствующие перегрузки операторов показаны ниже.
public static bool operator true(Criteria<T> criteria) {
return false;
}
public static bool operator false(Criteria<T> criteria) {
return false;
}
public static Criteria<T> operator &(Criteria<T> lhs, Criteria<T> rhs) {
return new AndCriteria<T>(lhs, rhs);
}
public static Criteria<T> operator |(Criteria<T> lhs, Criteria<T> rhs) {
return new OrCriteria<T>(lhs, rhs);
}
Ответ 3
От Microsoft (http://msdn.microsoft.com/en-us/library/6292hy1k.aspx):
До С# 2.0 операторы true и false использовались для создания пользовательских
nullable типов значений, которые были совместимы с типами, такими как SqlBool. Однако,
теперь язык обеспечивает встроенную поддержку типов с нулевыми значениями и
по возможности вы должны использовать их вместо перегрузки true и
ложных операторов.
Если вы хотите просто оценить свой объект на логическом уровне, удалите оператор true и операторные ложные перегрузки и просто используйте перегрузку bool.
Ответ 4
Отвечая на ваш последний вопрос: "Как я могу сделать свой тип, чтобы он мог проверить, истинны ли обе переменные?" - просто используйте оператор &
. Вся точка &&
заключается в коротком замыкании, так что второй аргумент не проверяется, когда это не необходимо.
Проверьте это:
Console.WriteLine(b1 & b2); // outputs 1
Console.WriteLine(b2 & b1); // outputs 1
На самом деле вам не хватает одного важного бита, который позволит вам использовать MyStruct (с &
и |
) как логическое - неявное преобразование в bool
:
public static implicit operator bool(MyStruct ms) {
return ms._i > 0;
}
Это позволяет использовать MyStruct (так же как и результат операторов) как таковой:
if (b1 & b2)
Console.WriteLine("foo");
Как последний, возможно самый важный, обратите внимание: проблема в вашем примере исходит из того, что вы хотите выполнять логические операции (проверьте, являются ли 2 экземпляра MyStruct
true
), но ваш оператор &
реализован неправильно для такой цели. Он работает в терминах двоичной арифметики, давая экземпляр MyStruct
со значением 1
при вызове с аргументами MyStruct(1)
(true
) и MyStruct(-1)
(false
). Так что в основном это (true & false) == true
. Вот почему b1 && b2
дает другой результат, чем b2 && b1
в вашем примере. Любая дальнейшая логика, основанная на этом операторе, будет нарушена и непредсказуема. Поведение &&
, которое реализовано в .NET с точки зрения false
и &
, подтверждает это.
EDIT: вы хотите использовать MyStruct
как логическое. Вы реализуете операторы true
и false
и ожидаете, что &&
и ||
будут работать в терминах логической логики. Однако вы реализуете &
в терминах бинарной арифметики (используя поля &
on int
), что делает эту реализацию &
несовместимой с логической логикой, которую вы ожидаете ((1 & -1) == 1
, что означает (true & false) == false
в вашей интерпретации логического значения MyStruct
). Теперь рассмотрим, что &&
вообще не является логическим оператором (он не возвращает bool
) - это короткое замыкание, реализованное как T.false(x) ? x : T.&(x, y)
. Обратите внимание, что он возвращает MyStruct
в вашем случае, который вы просто интерпретируете как true
или false
в зависимости от значения его поля. В нижней строке: вы ожидаете, что &&
выполнит логический тест для обоих операндов, но реализация .NET &&
использует вашу реализацию &
, которая несовместима с логической логикой, которую вы ожидаете.
Ответ 5
Точкой истинных/ложных операторов является предоставление логической логической семантики без необходимости неявного преобразования в bool
.
Если вы позволяете вашему типу неявно вводить в bool
, операторы true/false больше не нужны. Но если вы хотите только явное преобразование или отсутствие преобразования, но все же хотите разрешить ваш тип как условие в выражении if
или while
, вы можете использовать операторы true
и false
.