Книга: C# 4.0: полное руководство

Перегрузка логических операторов

Перегрузка логических операторов

Как вам должно быть уже известно, в C# предусмотрены следующие логические операторы: &, |, !, && и ||. Из них перегрузке, безусловно, подлежат только операторы &, | и !. Тем не менее, соблюдая определенные правила, можно извлечь также пользу из укороченных логических операторов && и ||. Все эти возможности рассматриваются ниже.

Простой способ перегрузки логических операторов

Рассмотрим сначала простейший случай. Если не пользоваться укороченными логическими операторами, то перегрузку операторов & и | можно выполнять совершенно естественным путем, получая в каждом случае результат типа bool. Аналогичный результат, как правило, дает и перегружаемый оператор !.

Ниже приведен пример программы, в которой демонстрируется перегрузка логических операторов !, & и | для объектов типа ThreeD. Как и в предыдущем примере, объект типа ThreeD считается истинным, если хотя бы одна из его координат не равна нулю. Если же все три координаты объекта равны нулю, то он считается ложным.

// Простой способ перегрyзки логическиx операторов
// !, | и & для объектов класса ThreeD.
using System;
// Класс для xранения треxмерныx координат.
class ThreeD {
  int x, y, z; // треxмерные координаты
  public ThreeD() { x = y = z = 0; }
  public ThreeD(int i, int j, int k) { x = i; y = j; z = k; }
  // Перегрyзить логический оператор |.
  public static bool operator |(ThreeD op1, ThreeD op2) {
    if (((op1.x != 0) || (op1.y != 0) || (op1.z != 0)) |
       ((op2.x != 0) || (op2.y != 0) || (op2.z != 0)))
      return true;
    else
      return false;
  }
  // Перегрyзить логический оператор &.
  public static bool operator &(ThreeD op1, ThreeD op2) {
    if (((op1.x != 0) && (op1.y != 0) && (op1.z != 0)) &
       ((op2.x != 0) && (op2.y != 0) && (op2.z != 0)))
      return true;
    else
      return false;
  }
  // Перегрyзить логический оператор !.
  public static bool operator !(ThreeD op) {
    if ((op.x != 0) || (op.y != 0) || (op.z != 0))
      return false;
    else
      return true;
  }
  // Вывести координаты X, Y, Z.
  public void Show() {
    Console.WriteLine(x + ", " + y + ", " + z);
  }
}
class TrueFalseDemo {
  static void Main() {
    ThreeD a = new ThreeD(5, 6, 7);
    ThreeD b = new ThreeD(10, 10, 10);
    ThreeD c = new ThreeD(0, 0, 0);
    Console.Write("Координаты точки a: ");
    a.Show();
    Console.Write("Координаты точки b: ");
    b.Show();
    Console.Write("Координаты точки с: ");
    c.Show();
    Console.WriteLine();
    if (!a) Console.WriteLine("Точка а ложна.");
    if (!b) Console.WriteLine("Точка b ложна.");
    if (!c) Console.WriteLine("Точка с ложна.");
    if (a & b) Console.WriteLine("a & b истинно.");
    else Console.WriteLine("a & b ложно.");
    if (a & c) Console.WriteLine("a & с истинно.");
    else Console.WriteLine("a & с ложно.");
    if (a | b) Console.WriteLine("a | b истинно.");
    else Console.WriteLine("a | b ложно.");
    if (a | c) Console.WriteLine("a | с истинно.");
    else Console.WriteLine("a | с ложно.");
  }
}

При выполнении этой программы получается следующий результат.

Координаты точки а: 5, 6, 1
Координаты точки b: 10, 10,    10
Координаты точки с: 0, 0, 0
Точка с ложна.
а & b истинно.
а & с ложно.
а | b истинно,
а | с истинно.

При таком способе перегрузки логических операторов &, | и ! методы каждого из них возвращают результат типа bool. Это необходимо для того, чтобы использовать рассматриваемые операторы обычным образом, т.е. в тех выражениях, где предполагается результат типа bool. Напомним, что для всех встроенных в C# типов данных результатом логической операции должно быть значение типа bool. Поэтому вполне разумно предусмотреть возврат значения типа bool и в перегружаемых вариантах этих логических операторов. Но, к сожалению, такой способ перегрузки пригоден лишь в том случае, если не требуются укороченные логические операторы.

Как сделать укороченные логические операторы доступными для применения

Для того чтобы применение укороченных логических операторов && и || стало возможным, необходимо соблюсти следующие четыре правила. Во-первых, в классе должна быть произведена перегрузка логических операторов & и |. Во-вторых, перегружаемые методы операторов & и | должны возвращать значение того же типа, что и у класса, для которого эти операторы перегружаются. В-третьих, каждый параметр должен содержать ссылку на объект того класса, для которого перегружается логический оператор. И в-четвертых, для класса должны быть перегружены операторы true и false. Если все эти условия выполняются, то укороченные логические операторы автоматически становятся пригодными для применения.

В приведенном ниже примере программы показано, как правильно реализовать логические операторы & и | в классе ThreeD, чтобы сделать доступными для применения укороченные логические операторы && и ||.

/* Более совершенный способ перегрyзки логическиx оперaторов !,  | и & для объектов клaссa ThreeD.
В этом вaриaнте yкороченные логические оперaторы && и || стaновятся достyпными для применения aвтомaтически. */
using System;
// Клaсс для xрaнения треxмерныx координaт,
class ThreeD {
  int x, y, z; // треxмерные координaты
  public ThreeD() { x = y = z = 0; }
  public ThreeD(int i, int j, int k) { x = i; y = j; z = k; }
  // Перегрyзить логический оперaтор | для yкороченного вычисления,
  public static ThreeD operator |(ThreeD op1, ThreeD op2) {
    if (((op1.x != 0) || (op1.y != 0) || (op1.z != 0)) |
       ((op2.x != 0) || (op2.y != 0) || (op2.z != 0)))
      return new ThreeD(1, 1, 1);
    else
      return new ThreeD(0, 0, 0);
  }
  // Перегрyзить логический оперaтор & для yкороченного вычисления,
  public static ThreeD operator &(ThreeD op1, ThreeD op2) {
    if (((op1.x != 0) && (op1.y != 0) && (op1.z != 0)) &
       ((op2.x != 0) && (op2.y != 0) && (op2.z != 0)))
      return new ThreeD(1, 1, 1);
    else
      return new ThreeD(0, 0, 0);
  }
  // Перегрyзить логический оперaтор !.
  public static bool operator !(ThreeD op) {
    if (op)
      return false;
    else
      return true;
  }
  // Перегрyзить оперaтор true.
  public static bool operator true(ThreeD op) {
    if ((op.x != 0) || (op.y != 0) || (op.z != 0))
      return true;    //xотя бы однa координaтa не рaвнa нyлю
    else
      return false;
  }
  // Перегрyзить оперaтор false.
  public static bool operator false(ThreeD op) {
    if ((op.x == 0) && (op.y == 0) && (op.z == 0))
      return true; //все координaты рaвны нyлю
    else
      return false;
  }
  // Ввести координaты X, Y, Z.
  public void Show() {
    Console.WriteLine(x + ", " + y + ", " + z);
  }
}
class TrueFalseDemo {
  static void Main() {
    ThreeD a = new ThreeD(5, 6, 7);
    ThreeD b = new ThreeD(10, 10, 10);
    ThreeD c = new ThreeD(0, 0, 0);
    Console.Write("Координaты точки a: ");
    a.Show();
    Console.Write("Координaты точки b: ");
    b.Show();
    Console.Write("Координaты точки с: ");
    c.Show();
    Console.WriteLine();
    if (a) Console.WriteLine("Точкa a истиннa.");
    if (b) Console.WriteLine("Точкa b истиннa.");
    if (c) Console.WriteLine("Точкa с истиннa.");
    if (!a) Console.WriteLine("Точкa a ложнa.");
    if (!b) Console.WriteLine("Точкa b ложнa.");
    if (!c) Console.WriteLine("Точкa с ложнa.");
    Console.WriteLine();
    Console.WriteLine("Применение логическиx оперaторов & и |");
    if (a & b) Console.WriteLine("a & b истинно.");
    else Console.WriteLine("a & b ложно.");
    if (a & c) Console.WriteLine("a & с истинно.");
    else Console.WriteLine("a & с ложно.");
    if (a | b) Console.WriteLine("a | b истинно.");
    else Console.WriteLine("a | b ложно.");
    if (a | c) Console.WriteLine("a | с истинно.");
    else Console.WriteLine("a | с ложно.");
    Console.WriteLine();
    //a теперь применить yкороченные логические оперaторы.
    Console.WriteLine("Применение yкороченныx" +
           "логическиx оперaторов && и ||");
    if (a && b) Console.WriteLine("a && b истинно.");
    else Console.WriteLine("a && b ложно.");
    if (a && c) Console.WriteLine("a && с истинно.");
    else Console.WriteLine("a && с ложно.");
    if (a || b) Console.WriteLine("a || b истинно.");
    else Console.WriteLine("a || b ложно.");
    if (a || c) Console.WriteLine("a || с истинно.");
    else Console.WriteLine("a || с ложно.");
  }
}

Выполнение этой программы приводит к следующему результату.

Координаты точки а: 5, 6, 7
Координаты точки b: 10, 10, 10
 Координаты точки с: 0, 0, 0
Точка а истинна
Точка b истинна
Точка с ложна.
Применение логических операторов & и |
а & b истинно,
а & с ложно,
а | b истинно,
а | с истинно.
Применение укороченных логических операторов && и ||
а && b истинно,
а && с ложно,
а И b истинно,
а И с истинно.

Рассмотрим более подробно, каким образом реализуются логические операторы & и |. Они представлены в следующем фрагменте кода.

// Перегрузить логический оператор | для укороченного вычисления,
public static ThreeD operator | (ThreeD op1, ThreeD op2)
{
  if(((op1.x != 0) || (op1.у != 0) || (op1.z != 0)) |
     ((op2.x != 0) || (op2.у != 0) || (op2.z != 0)))
    return new ThreeD(1, 1, 1) ;
  else
    return new ThreeD(0, 0, 0);
}
// Перегрузить логический оператор & для укороченного вычисления,
public static ThreeD operator & (ThreeD op1, ThreeD op2)
{
  if(((op1.x != 0) && (op1.y != 0) && (op1.z != 0)) &
     ((op2.x != 0) && (op2.y != 0) && (op2.z != 0)))
    return new ThreeD(1, 1,1);
  else
    return new ThreeD (0, 0, 0);
}

Прежде всего обратите внимание на то, что методы обоих перегружаемых логических операторов теперь возвращают объект типа ThreeD. И особенно обратите внимание на то, как формируется этот объект. Если логическая операция дает истинный результат, то создается и возвращается истинный объект типа ThreeD, у которого хотя бы одна координата не равна нулю. Если же логическая операция дает ложный результат, то соответственно создается и возвращается ложный объект. Таким образом, результатом вычисления логического выражения а & b в следующем фрагменте кода:

if(а & b) Console.WriteLine("а & b истинно.");
else Console.WriteLine("а & b ложно.");

является объект типа ThreeD, который в данном случае оказывается истинным. А поскольку операторы true и false уже определены, то созданный объект типа ThreeD подвергается действию оператора true и в конечном итоге возвращается результат типа bool. В данном случае он равен true, а следовательно, условный оператор if успешно выполняется.

Благодаря тому что все необходимые правила соблюдены, укороченные операторы становятся доступными для применения к объектам ThreeD. Они действуют следующим образом. Первый операнд проверяется с помощью операторного метода operator true (для оператора ||) или же с помощью операторного метода operator false (для оператора &&). Если удается определить результат данной операции, то соответствующий перегружаемый оператор (& или |) далее не выполняется. В противном случае перегружаемый оператор (& или | соответственно) используется для определения конечного результата. Следовательно, когда применяется укороченный логический оператор && или ||, то соответствующий логический оператор & или | вызывается лишь в том случае, если по первому операнду невозможно определить результат вычисления выражения. В качестве примера рассмотрим следующую строку кода из приведенной выше программы.

if(а || с) Console.WriteLine("а || с истинно.");

В этой строке кода сначала применяется оператор true к объекту а. В данном случае объект а истинен, и поэтому использовать далее операторный метод | нет необходимости. Но если переписать данную строку кода следующим образом:

if(с || a) Console.WriteLine ("с || а истинно.");

то оператор true был бы сначала применен к объекту с, который в данном случае ложен. А это означает, что для определения истинности объекта а пришлось бы далее вызывать операторный метод |.

Описанный выше способ применения укороченных логических операторов может показаться, на первый взгляд, несколько запутанным, но если подумать, то в таком применении обнаруживается известный практический смысл. Ведь благодаря перегрузке операторов true и false для класса компилятор получает разрешение на применение укороченных логических операторов, не прибегая к явной их перегрузке. Это дает также возможность использовать объекты в условных выражениях. И вообще, логические операторы & и | лучше всего реализовывать полностью, если, конечно, не требуется очень узко направленная их реализация.

Операторы преобразования

Иногда объект определенного класса требуется использовать в выражении, включающем в себя данные других типов. В одних случаях для этой цели оказывается пригодной перегрузка одного или более операторов, а в других случаях — обыкновенное преобразование типа класса в целевой тип. Для подобных ситуаций в C# предусмотрена специальная разновидность операторного метода, называемая оператором преобразования.Такой оператор преобразует объект исходного класса в другой тип. Операторы преобразования помогают полностью интегрировать типы классов в среду программирования на С#, разрешая свободно пользоваться классами вместе с другими типами данных, при условии, что определен порядок преобразования в эти типы.

Существуют две формы операторов преобразования: явная и неявная. Ниже они представлены в общем виде:

public static explicit operator целевой_тип{исходный_тип v) {return значение;}
public static implicit operator целевой_тип(исходный_тип v) {return значение;}

где целевой_тип обозначает тот тип, в который выполняется преобразование; исходный_тип — тот тип, который преобразуется; значение — конкретное значение, приобретаемое классом после преобразования. Операторы преобразования возвращают данные, имеющие целевой_тип, причем указывать другие возвращаемые типы данных не разрешается.

Если оператор преобразования указан в неявной форме (implicit), то преобразование вызывается автоматически, например, в том случае, когда объект используется в выражении вместе со значением целевого типа. Если же оператор преобразования указан в явной форме (explicit), то преобразование вызывается в том случае, когда выполняется приведение типов. Для одних и тех же исходных и целевых типов данных нельзя указывать оператор преобразования одновременно в явной и неявной форме.

Создадим оператор преобразования специально для класса ThreeD, чтобы продемонстрировать его применение. Допустим, что требуется преобразовать объект типа ThreeD в целое значение, чтобы затем использовать его в целочисленном выражении. Такое преобразование требуется, в частности, для получения произведения всех трех координат объекта. С этой целью мы воспользуемся следующей неявной формой оператора преобразования.

public static implicit operator int(ThreeD op1)
{
  return op1.x * op1.у * op1.z;
}

Ниже приведен пример программы, демонстрирующей применение этого оператора преобразования.

// Пример применения оператора неявного преобразования,
using System;
// Класс для хранения трехмерных координат,
class ThreeD {
  int x, y, z; // трехмерные координаты
  public ThreeD() { x = y = z = 0; }
  public ThreeD(int i, int j, int k) { x = i; y = j; z = k; }
  // Перегрузить бинарный оператор +.
  public static ThreeD operator +(ThreeD op1, ThreeD op2) {
    ThreeD result = new ThreeD();
    result.x = op1.x + op2.x;
    result.x = op1.y + op2.y;
    result.z = op1.z + op2.z;
    return result;
  }
  // Неявное преобразование объекта типа ThreeD к типу int.
  public static implicit operator int(ThreeD op1) {
    return op1.x * op1.y * op1.z;
  }
  // Вывести координаты X, Y, Z.
  public void Show() {
    Console.WriteLine(x + ", " + y + ", " + z);
  }
}
class ThreeDDemo {
  static void Main() {
    ThreeD a = new ThreeD(1, 2, 3);
    ThreeD b = new ThreeD(10, 10, 10);
    ThreeD c = new ThreeD();
    int i;
    Console.Write("Координаты точки a: ");
    a.Show();
    Console.WriteLine();
    Console.Write("Координаты точки b: ");
    b.Show();
    Console.WriteLine();
    c = a + b; // сложить координаты точек а и b
    Console.Write("Результат сложения a + b: ");
    c.Show();
    Console.WriteLine();
    i = a; // преобразовать в тип int
    Console.WriteLine("Результат присваивания i = a: " + i);
    Console.WriteLine();
    i = a * 2 - b; // преобразовать в тип int
    Console.WriteLine("Результат вычисления выражения a * 2 - b " + i);
  }
}

Вот к какому результату приводит выполнение этой программы.

Координаты точки а: 1, 2, 3
Координаты точки b: 10, 10, 10
Результат сложения а+b: 11, 12, 13
Результат присваивания i = а: 6
Результат вычисления выражения а * 2 - b: -988

Как следует из приведенного выше примера программы, когда объект типа ThreeD используется в таком целочисленном выражении, как i = а, происходит его преобразование. В этом конкретном случае преобразование приводит к возврату целого значения 6, которое является произведением координат точки а, хранящихся в объекте того же названия. Но если для вычисления выражения преобразование в тип int не требуется, то оператор преобразования не вызывается. Именно поэтому операторный метод operator int() не вызывается при вычислении выражения с = а + b.

Но для различных целей можно создать разные операторы преобразования. Так, для преобразования объекта типа ThreeD в тип double можно было бы определить второй оператор преобразования. При этом каждый вид преобразования выполнялся бы автоматически и независимо от другого.

Оператор неявного преобразования применяется автоматически в следующих случаях: когда в выражении требуется преобразование типов; методу передается объект; осуществляется присваивание и производится явное приведение к целевому типу. С другой стороны, можно создать оператор явного преобразования, вызываемый только тогда, когда производится явное приведение типов. В таком случае оператор явного преобразования не вызывается автоматически. В качестве примера ниже приведен вариант предыдущей программы, переделанный для демонстрации явного преобразования в тип int.

// Применить явное преобразование,
using System;
// Класс для хранения трехмерных координат,
class ThreeD {
  int x, y, z; // трехмерные координаты
  public ThreeD() { x = y = z = 0; }
  public ThreeD(int i, int j, int k) { x = i; y = j; z = k; }
  // Перегрузить бинарный оператор +.
  public static ThreeD operator +(ThreeD op1, ThreeD op2) {
    ThreeD result = new ThreeD();
    result.x = op1.x + op2.x;
    result.y = op1.y + op2.y;
    result.z = op1.z + op2.z;
    return result;
  }
  // Выполнить на этот раз явное преобразование типов,
  public static explicit operator int(ThreeD op1) {
    return op1.x * op1.y * op1.z;
  }
  // Вывести координаты X, Y, Z.
  public void Show()
  {
  Console.WriteLine(x + ", " + y + ", " + z);
  }
}
class ThreeDDemo {
  static void Main() {
    ThreeD a = new ThreeD(1, 2, 3);
    ThreeD b = new ThreeD(10, 10, 10);
    ThreeD c = new ThreeD(); int i;
    Console.Write("Координаты точки a: ");
    a.Show();
    Console.WriteLine();
    Console.Write("Координаты точки b: ");
    b.Show();
    Console.WriteLine();
    c = a + b; // сложить координаты точек а и b
    Console.Write("Результат сложения a + b: ");
    c.Show();
    Console.WriteLine();
    i = (int)a; // преобразовать в тип int явно,
    // поскольку указано приведение типов
    Console.WriteLine("Результат присваивания i = а: " + i);
    Console.WriteLine();
    i = (int)a * 2 - (int)b; // явно требуется приведение типов
    Console.WriteLine("Результат вычисления выражения а * 2 - b: " + i);
  }
}

Оператор преобразования теперь указан в явной форме, и поэтому преобразование должно быть явно приведено к типу int. Например, следующая строка кода не будет скомпилирована, если исключить приведение типов.

i = (int) а; // преобразовать в тип int явно,
// поскольку указано приведение типов

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

•    Исходный или целевой тип преобразования должен относиться к классу, для которого объявлено данное преобразование. В частности, нельзя переопределить преобразование в тип int, если оно первоначально указано как преобразование в тип double.

•    Нельзя указывать преобразование в класс object или же из этого класса.

•    Для одних и тех же исходных и целевых типов данных нельзя указывать одновременно явное и неявное преобразование.

•    Нельзя указывать преобразование базового класса в производный класс. (Подробнее о базовых и производных классах речь пойдет в главе 11.)

•    Нельзя указывать преобразование в интерфейс или же из него. (Подробнее об интерфейсах — в главе 12.)

Помимо указанных выше ограничений, имеется ряд рекомендаций, которыми обычно руководствуются при выборе операторов явного или неявного преобразования. Несмотря на все преимущества неявных преобразований, к ним следует прибегать только в тех случаях, когда преобразованию не свойственны ошибки. Во избежание подобных ошибок неявные преобразования должны быть организованы только в том случае, если удовлетворяются следующие условия. Во-первых, информация не теряется, например, в результате усечения, переполнения или потери знака. И во-вторых, преобразование не приводит к исключительной ситуации. Если же неявное преобразование не удовлетворяет этим двум условиям, то следует выбрать явное преобразование.

Оглавление книги


Генерация: 1.416. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз