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

 


Споріднені теми