C#. Перегрузка оператора приведения типа ()

Перегрузка оператора приведения типа (). Перегрузка операторов truefalse. Перегрузка логических операторов &&, ||


Содержание


Поиск на других ресурсах:

1. Перегрузка оператора (). Общие сведения

Оператор () может быть перегружен в классе в случае приведения из одного типа в другой.

Поскольку класс, в котором реализуется перегрузка оператора () является типом, то приведение может иметь две реализации:

1. Приведение из любого типа в тип нашего класса:

Class1 = Class2;

здесь

  • Class1 – класс, в котором перегружен оператор ();
  • Class2 – другой клас.

2. Приведение из типа класса, в котором перегружен оператор (), к любому произвольному типу

Class2 = Class1;

здесь

  • Class1 – класс, в котором перегружен оператор ();
  • Class2 – класс, к которому приводится Class1.

Приведение типа не зависит от того, какой тип приводится, ссылочный или значимый.

Как известно, приведение типов есть:

  • явное – это случай, когда возможна потеря данных в результате приведения. Например, приведение int=>short, int=>uint, double=>int и т.п.;
  • неявное – случай, когда не нужно указывать явного приведения, поскольку потеря данных не происходит (int=>int, Point=>Point, …).

В зависимости от приведения (явного/неявного) объявление метода, перегружающего оператор приведения () содержит одно из двух слов:

  • implicit – задает неявное преобразование. Это преобразование можно задавать без риска утраты точности;
  • explicit – задает явное преобразование. Это преобразование используется, если возможна потеря данных или даже возникновение исключительной ситуации.

 

1.1. Неявное (implicit) приведение Class1=Class2. Общая форма. Пример

Неявное приведение типа выполняется при выполнении простого присваивания

obj1 = obj2;

здесь

  • obj1 – экземпляр некоторого класса Class1, являющийся приемником значения экземпляра obj2;
  • obj2 – экземпляр класса-источника Class2.

Учитывая вышесказанное, общая форма использования операторного метода operator()() неявного преобразования (implicit) в классе с именем Class1 имеет вид:

public static implicit operator Class1 (Class2 obj)
{
  // Class1 <= Class2
  // ...
}

здесь

  • Class1 – класс, к которому осуществляется приведение;
  • Class2 – класс, выступающий в качестве исходного типа.

Если в классе объявляется неявное приведение типов Class1<=Class2, то допускается также задавать явное приведение в операции присваивания

obj1 = (Class1)obj2;

 

1.2. Явное (explicit) приведение типов Class1 = (Class1)Class2. Пример

Явное приведение типов необходимо в случаях, когда происходит потеря данных. Однако это не обязательно при перегрузке оператора () в классе. Способ кодировки метода, перегружающего оператор (), определяется по собственному усмотрению в зависимости от поставленной задачи.
Для двух экземпляров явное приведение типа выглядит следующим образом:

obj1 = (Class1)obj2;

здесь

  • obj1 – экземпляр класса Class1, который есть приемником;
  • obj2 – экземпляр некоторого класса Class2, который приводится к типу Class1.

При использовании явного приведения типов объявление операторного метода, который перегружает оператор (), имеет вид:

public static explicit operator Class1 (Class2 obj)
{
  // Class1 <= Class2
  // ...
}

Если в классе объявлено явное (explicit) приведение типа из Class2 в Class1 (Class1<=Class2), то операция неявного присвоения

obj1 = obj2;

без указания явного приведения выдаст ошибку компиляции.

 

1.3. Пример перегрузки оператора () для типов Line и Rectangle. Демонстрируется явное и неявное приведение типа

В примере показана перегрузка оператора () для типов Line и Rectangle.

Line => Rectangle
Rectangle <= Line

В классе Line формируются поля с координатами крайних точек. Эти точки могут быть восприняты как координаты углов прямоугольника Rectangle. Таким образом, данные не теряются, просто один тип класса конвертируется в другой.

using System;

namespace ConsoleApp6
{
  // Класс, описывающий линию
  class Line
  {
    // Координаты точек линии
    private double x1, y1, x2, y2;

    // Конструктор
    public Line(double x1, double y1, double x2, double y2)
    {
      this.x1 = x1;
      this.y1 = y1;
      this.x2 = x2;
      this.y2 = y2;
    }

    // Свойства доступа
    public double X1 { get { return x1; } }
    public double Y1 { get { return y1; } }
    public double X2 { get { return x2; } }
    public double Y2 { get { return y2; } }

    // Метод, выводящий состояние объекта Line
    public void Print(string msg)
    {
      Console.Write(msg + " => ");
      Console.WriteLine("( " + x1 + "; " + y1 + ") - ( " + x2 + "; " + y2 + ")");
    }

    // Метод, который перегружает оператор приведения Line <=  Rectangle,
    // используется неявное преобразование (implicit)
    // Line <= Rectangle
    public static implicit operator Line(Rectangle r)
    {
      return new Line(r.X1, r.Y1, r.X2, r.Y2);
    }

    // Явне преобразование (explicit)
    // Rectangle <= Line
    public static explicit operator Rectangle(Line l)
    {
      return new Rectangle(l.X1, l.Y1, l.X2, l.Y2);
    }
  }

  // Класс, описывающий прямоугольник
  class Rectangle
  {
    // Внутренние поля - координаты углов прямоугольника
    private double x1, y1, x2, y2;

    // Конструктор
    public Rectangle(double x1, double y1, double x2, double y2)
    {
      this.x1 = x1;
      this.y1 = y1;
      this.x2 = x2;
      this.y2 = y2;
    }

    // Свойства доступа
    public double X1 { get { return x1; } }
    public double Y1 { get { return y1; } }
    public double X2 { get { return x2; } }
    public double Y2 { get { return y2; } }

    // Метод, который выводит внутреннее состояние экземпляра класса
    public void Print(string msg)
    {
      Console.WriteLine(msg + " => x1 = " + x1 + "; y1 = " + y1 +
        "; x2 = " + x2 + "; " + "y2 = " + y2);
    }
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      // Вызов неявный (implicit)

      // 1. Реализовать implicit-присваивание
      // 1.1. Объявить экземпляр класса Rectangle
      Rectangle r1 = new Rectangle(1, 1, 3, 8);

      // 1.2. Вызвать оператор приведения типа ()
      Line l1 = r1; // Line <= Rectangle
      l1.Print("l1");

      // 2. Реализовать explicit-присваивание
      // 2.1. Объявить экземпляр класса Line
      Line l2 = new Line(5, 8, 1, 3);

      // 2.2. Выполнить явное присваивание
      Rectangle r2;
      r2 = (Rectangle)l2; // Rectangle <= Line
      r2.Print("r2");

      Console.ReadKey();
    }
  }
}

Проанализируем вышеприведенный код.

Операторный метод, приводящий из типа Rectangle в тип Line, объявлен с ключевым словом implicit. В этом случае допускается неявное приведение

l1 = r1; // Line <= Rectangle

Операторный метод, выполняющий обратное присвоение из типа Line в тип Rectangle объявлен с ключевым словом explicit. Здесь нужно указывать только явное приведение

r2 = (Rectangle)l2; // Rectangle <= Line

Если не задать явное приведение, то компилятор выдаст ошибку.

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

l1 => ( 1; 1) - ( 3; 8)
r2 => x1 = 5; y1 = 8; x2 = 1; y2 = 3

 

1.4. Пример перегрузки оператора () для типов Point<=>Circle. Демонстрация приведения для разных видов типов: int<=Point, Circle<=Point, Point<=Circle, double<=Circle

В примере происходит преобразование между классами Point <=> Circle. В классе Point перегружаются методы

public static implicit operator int(Point pt);
public static explicit operator Circle(Point pt);

В классе Circle перегружаются операторы

public static explicit operator double(Circle cr);
public static implicit operator Point(Circle cr);
public static implicit operator Circle(int value);

Данный пример наглядно показывает возможности перегрузки оператора () для взаимного приведения между значимыми стандартными типами (int, double) и собственными типами (Point, Circle).

Текст демонстрационной программы следующий.

using System;

namespace ConsoleApp6
{
  // Класс, описывающий точку на координатной плоскости
  class Point
  {
    // Внутренние поля класса
    private double x, y;

    // Конструктор
    public Point(double x, double y)
    {
      this.x = x;
      this.y = y;
    }

    // Свойства доступа
    public double X { get { return x; } }
    public double Y { get { return y; } }

    // Метод, который выводит координаты точки
    public void Print(string msg)
    {
      Console.WriteLine(msg + " => ( " + x + "; " + y + ")");
    }

    // Методы, перегружающие оператор приведения типа ()
    // int <= Point: obj1 = obj2
    public static implicit operator int(Point pt)
    {
      return (int)(pt.x + pt.y); // сумма координат
    }

    // Circle<=Point: obj1 = (Circle)obj2
    public static explicit operator Circle(Point pt)
    {
      return new Circle(pt.X, pt.Y, 0.0);
    }
  }

  // Класс, описывающий окружность
  class Circle
  {
    // Внутренние поля
    private double x, y, radius;

    // Конструктор
    public Circle(double x, double y, double radius)
    {
      this.x = x;
      this.y = y;
      this.radius = radius;
    }

    // Свойства доступа к полям класса
    public double X { get { return x; } }
    public double Y { get { return y; } }
    public double Radius { get { return radius; } }

    // Метод, который выводит внутреннее состояние экземпляра
    public void Print(string msg)
    {
      System.Console.WriteLine(msg + " => x = " + x + "; y = " + y + "; radius = " + radius);
    }

    // Методы, которые перегружают оператор ()
    // Явная перегрузка: double<=Circle; obj1 = (double)obj2
    public static explicit operator double(Circle cr)
    {
      // Вернуть площадь окружности
      return Math.PI * cr.radius * cr.radius;
    }

    // Неявная перегрузка: Point<=Circle; obj1 = obj2
    public static implicit operator Point(Circle cr)
    {
      return new Point(cr.X, cr.Y);
    }

    // Неявная перегрузка: Circle<=int; obj1 = obj2
    public static implicit operator Circle(int value)
    {
      return new Circle(value, value, value);
    }
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      // 1. Объявить экземпляры классов Point, Circle
      Point pt1 = new Point(2, 9);
      Circle cr1 = new Circle(1, 9, 8);

      // 2. Продемонстрировать методы приведения
      // 2.1. int <= Point - неявное
      int t = pt1; // вызывается Point.operator int(Point)
      Console.WriteLine("t = " + t);

      // 2.2. Circle <= Point - явное
      Circle cr2;
      cr2 = (Circle)pt1; // вызывается Point.operator Circle(Point)
      cr2.Print("cr2");

      // 2.3. double <= Circle - явное
      double x = (double)cr1; // вызывается Circle.operator double(Circle)
      Console.WriteLine("x = " + x);

      // 2.4. Point <= Circle - неявное
      Point pt2;
      pt2 = cr1; // вызывается Circle.operator Point(Circle)
      pt2.Print("pt2");

      // 2.5. Circle <= int - неявное
      Circle cr3;
      cr3 = 8; // вызывается Circle.operator Circle(int)
      cr3.Print("cr3");

      Console.ReadKey();
    }
  }
}

Результат

t = 11
cr2 => x = 2; y = 9; radius = 0
x = 201.061929829747
pt2 => ( 1; 9)
cr3 => x = 8; y = 8; radius = 8

 

1.5. Ограничения, накладываемые на оператор приведения типа ()

На оператор приведения типа накладываются следующие ограничения:

1. Если классы образуют иерархию и один из них есть производным от другого, то между этими классами невозможно провести приведение.

2. Приведение можно реализовывать только в одном из типов (классов):

  • только в целевом типе;
  • только в типе назначения.

 

2. Перегрузка операторов true, false. Пример

При перегрузке операторов true и false задается критерий истинности и фальши для типа данных, которым является текущий класс. После перегрузки, объекты этого типа (класса) можно использовать в логических выражениях и операторах if, while, do-while, for.

Операторы true и false перегружаются в паре. Общая форма перегрузки операторов имеет вид

class ClassName
{
  ...

  public static bool operator true(ClassName obj)
  {
    // Код, который возвращает true если истина
    // ...
  }

  public static bool operator false(ClassName obj)
  {
    // Код, который возвращает false если истина,
    // иначе возвращается true
    // ...
  }
}

Пример.

В примере объявляется класс Rectangle (Прямоугольник), формируемый на основе координат левого верхнего (x1; y1) и правого нижнего углов (x2; y2). В классе перегружаются операторы true и false. Операторы true и false определяют, является (не является) ли прямоугольник квадратом.

using System;

namespace ConsoleApp6
{
  // Класс Прямоугольник
  class Rectangle
  {
    // Внутренние данные - координаты крайних углов прямоугольника
    private double x1, y1, x2, y2;

    // Конструктор
    public Rectangle(double x1, double y1, double x2, double y2)
    {
      this.x1 = x1;
      this.y1 = y1;
      this.x2 = x2;
      this.y2 = y2;
    }

    // Свойства доступа к полям класса
    public double X1 { get { return x1; } }
    public double Y1 { get { return y1; } }
    public double X2 { get { return x2; } }
    public double Y2 { get { return y2; } }

    // Метод, выводящий координаты прямоугольника
    public void Print(string msg)
    {
      Console.Write(msg + " => ");
      Console.WriteLine("( " + x1 + "; " + y1 + ") - ( " + x2 + "; " + y2 + ")");
    }

    // Операторный метод, перегружающий оператор true
    public static bool operator true(Rectangle r)
    {
      return (Math.Abs(r.x1 - r.x2) == Math.Abs(r.y1 - r.y2));
    }

    // Перегрузка оператора false
    public static bool operator false(Rectangle r)
    {
      return (Math.Abs(r.x1 - r.x2) != Math.Abs(r.y1 - r.y2));
    }
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      // 1. Объявить 2 экземпляра класса Rectangle
      Rectangle r1 = new Rectangle(1, 1, 3, 3); // это квадрат
      Rectangle r2 = new Rectangle(1, 2, 8, 0); // это не квадрат

      // 2. Вызов перегруженного оператора true
      if (r1)
        Console.WriteLine("r1 is square.");
      else
        Console.WriteLine("r1 is not square.");

      // 3. Вызов перегруженного оператора false
      if (r2)
        Console.WriteLine("r2 is square.");
      else
        Console.WriteLine("r2 is not square.");

      Console.ReadKey();
    }
  }
}

Результат

r1 is square.
r2 is not square.

 

3. Перегрузка логических операторов &&, ||. Пример

Логические операторы && и || сами по себе не перегружаются. Однако они могут быть смоделированы в операторах & и | которые можно перегружать. Для этого требуется выполнение следующих условий:

  • в классе должны быть перегружены операторы true и false;
  • в классе должны быть перегружены логические операторы & и |;
  • операторные методы (operator&() и operator|()) должны возвращать тип класса, в котором осуществляется перегрузка;
  • параметрами операторных методов, перегружаемых операторами & и | должны быть ссылки на класс, в котором эти методы перегружаются.

Пример.

В следующем примере показывается перегрузка логических операторов &&, ||. Объявляется класс Point3D, реализующий точку в трехмерном пространстве. В классе объявляются:

  • внутренние скрытые (private) поля x, y, z являющиеся координатами точки в трехмерном пространстве;
  • конструктор Point3D();
  • свойства X, Y, Z доступа соответственно к полям x, y, z;
  • операторный метод operator true(), который перегружает оператор true. Метод возвращает значение true, если точка не лежит в начале координат (0; 0; 0);
  • операторный метод operator false(), который перегружает оператор false. Метод возвращает значение false, если точка находится в начале координат;
  • операторный метод operator|(), который перегружает оператор | (логическое «ИЛИ»). Этот операторный метод моделирует работу оператора ||;
  • операторный метод operator&(), который перегружает оператор & (логические «И»). Этот операторный метод моделирует работу оператора &&;
  • метод Print(), выводящий состояние экземпляра (координаты x, y, z).

 

using System;

namespace ConsoleApp6
{
  // Класс Point3D - описывает точку в трехмерном пространстве.
  // Класс демонстрирует перегрузку операторов && и ||
  class Point3D
  {
    // Координаты точки
    private double x, y, z;

    // Конструктор
    public Point3D(double x, double y, double z)
    {
      this.x = x;
      this.y = y;
      this.z = z;
    }

    // Свойства доступа к x, y, z
    public double X { get { return x; } }
    public double Y { get { return y; } }
    public double Z { get { return z; } }

    // Метод, который перегружает оператор true
    public static bool operator true(Point3D pt)
    {
      return !((pt.x == 0) && (pt.y == 0) && (pt.z == 0));
    }

    // Метод, который перегружает оператор false
    public static bool operator false(Point3D pt)
    {
      return (pt.x == 0) && (pt.y == 0) && (pt.z == 0);
    }

    // Метод, который перегружает оператор &
    public static Point3D operator &(Point3D p1, Point3D p2)
    {
      if (((p1.x != 0) && (p1.y != 0) && (p1.z != 0)) &&
        ((p2.x != 0) && (p2.y != 0) && (p2.z != 0)))
        return p2;
      return new Point3D(0, 0, 0);
    }

    // Метод, который перегружает оператор |
    public static Point3D operator |(Point3D p1, Point3D p2)
    {
      if (((p1.x != 0) || (p1.y != 0) || (p1.z != 0)) ||
        ((p2.x != 0) || (p2.y != 0) || (p2.z != 0)))
        return p2;
      return new Point3D(0, 0, 0);
    }
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      // 1. Создать 2 экземпляра класса Point3D
      Point3D pt1 = new Point3D(1, 1, 7);
      Point3D pt2 = new Point3D(0, 0, 4);

      // 2. Вызвать смоделированные перегруженные операторы && та ||
      if (pt1 && pt2)
        Console.WriteLine("pt1 && pt2 => true");
      else
        Console.WriteLine("pt1 && pt2 => false");

      if (pt1 || pt2)
        Console.WriteLine("pt1 || pt2 => true");
      else
        Console.WriteLine("pt1 || pt2 => false");

      Console.ReadKey();
    }
  }
}

Результат

pt1 && pt2 => false
pt1 || pt2 => true

 


Содержание