C#. Overloading the cast operator ()

Overloading the cast operator (). Overloading truefalse operators. Overloading the logical operators &&, ||


Contents


Search other resources:

1. Overloading the cast operator (). General information

The () operator can be overloaded in a class when cast from one type to another.

Since the class in which the overload of the operator () is implemented is a type, the cast can have two implementations:

1. Cast from any type to the type of our class:

Class1 = Class2;

here

  • Class1 – class in which operator () is overloaded;
  • Class2 – another class.

2. Cast from the type of the class in which the operator () is overloaded to any arbitrary type

Class2 = Class1;

here

  • Class1 – class in which operator () is overloaded;
  • Class2 – class to which Class1 is cast.

Type cast is independent of whether the type being cast is a reference type or a value type.

As you know, type cast is:

  • explicit – the case where data loss is possible as a result of the cast. For example, cast int=>short, int=>uint, double=>int, etc.;
  • implicit – the case when it is not necessary to specify an explicit cast, since no data loss occurs (int=>int, Point=>Point, …).

Depending on the cast (explicit/implicit), the declaration of a method that overloads the cast operator () contains one of two words:

  • implicit – specifies an implicit conversion. This conversion can be specified without the risk of loss of precision;
  • explicit—specifies an explicit conversion. This conversion is used if there is a possibility of data loss or even an exception.

  

1.1. Implicit cast Class1=Class2. General form. Example

An implicit cast is performed when performing a simple assignment

obj1 = obj2;

here

  • obj1 – an instance of some class Class1, which is the receiver of the value of an instance of obj2;
  • obj2 – an instance of the source class Class2.

Given the above, the general form of using the implicit operator method operator()() in a class named Class1 is:

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

here

  • Class1 – the class to which the cast is performed;
  • Class2 – the class that acts as the initial type.

If an implicit cast Class1<=Class2 is declared in a class, then it is also allowed to specify an explicit cast in the assignment operation

obj1 = (Class1)obj2;

  

1.2. Explicit cast Class1 = (Class1)Class2. Example

Explicit type cast is necessary in cases where data loss occurs. However, this is not necessary when overloading the () operator in a class. The method of encoding a method that overloads the operator () is determined at one’s own discretion, depending on the task. For two instances, an explicit cast looks like this:

obj1 = (Class1)obj2;

here

  • obj1 – an instance of class Class1, which is the receiver;
  • obj2 – an instance of some class Class2 that is cast to type Class1.

When using explicit type cast, the declaration of an operator method that overloads the () operator is:

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

If a class declares an explicit cast from Class2 to Class1 (Class1<=Class2), then the implicit assignment operation

obj1 = obj2;

without specifying an explicit cast will give a compilation error.

  

1.3. An example of operator overloading () for Line and Rectangle types. Demonstrates explicit and implicit type cast

The example shows an overload of the () operator for the Line and Rectangle types.

Line => Rectangle
Rectangle <= Line

In the Line class, fields with coordinates of extreme points are formed. These points can be thought of as the coordinates of the corners of the Rectangle. Thus, no data is lost, just one class type is converted to another.

using System;

namespace ConsoleApp6
{
  // A class that describes a line
  class Line
  {
    // Coordinates of line points
    private double x1, y1, x2, y2;

    // Constructor
    public Line(double x1, double y1, double x2, double y2)
    {
      this.x1 = x1;
      this.y1 = y1;
      this.x2 = x2;
      this.y2 = y2;
    }

    // Access properties
    public double X1 { get { return x1; } }
    public double Y1 { get { return y1; } }
    public double X2 { get { return x2; } }
    public double Y2 { get { return y2; } }

    // Method that outputs the state of the Line object
    public void Print(string msg)
    {
      Console.Write(msg + " => ");
      Console.WriteLine("( " + x1 + "; " + y1 + ") - ( " + x2 + "; " + y2 + ")");
    }

    // Method that overloads the Line <= Rectangle cast operator,
    // the implicit conversion is performed
    // Line <= Rectangle
    public static implicit operator Line(Rectangle r)
    {
      return new Line(r.X1, r.Y1, r.X2, r.Y2);
    }

    // Explicit conversion
    // Rectangle <= Line
    public static explicit operator Rectangle(Line l)
    {
      return new Rectangle(l.X1, l.Y1, l.X2, l.Y2);
    }
  }

  // Class that describes a rectangle
  class Rectangle
  {
    // Internal fields - coordinates of rectangle corners
    private double x1, y1, x2, y2;

    // Constructor
    public Rectangle(double x1, double y1, double x2, double y2)
    {
      this.x1 = x1;
      this.y1 = y1;
      this.x2 = x2;
      this.y2 = y2;
    }

    // Access properties
    public double X1 { get { return x1; } }
    public double Y1 { get { return y1; } }
    public double X2 { get { return x2; } }
    public double Y2 { get { return y2; } }

    // Method that outputs the internal state of an instance of a class
    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 call

      // 1. Implement implicit assignment
      // 1.1. Declare the object of Rectangle class
      Rectangle r1 = new Rectangle(1, 1, 3, 8);

      // 1.2. Call the type cast operator ()
      Line l1 = r1; // Line <= Rectangle
      l1.Print("l1");

      // 2. Implement explicit assignment
      // 2.1. Declare an instance of the Line class
      Line l2 = new Line(5, 8, 1, 3);

      // 2.2. Perform explicit assignment
      Rectangle r2;
      r2 = (Rectangle)l2; // Rectangle <= Line
      r2.Print("r2");

      Console.ReadKey();
    }
  }
}

Let’s analyze the above code.

The operator method that casts from the Rectangle type to the Line type is declared with the implicit keyword. In this case, an implicit cast is allowed.

l1 = r1; // Line <= Rectangle

The operator method that performs the reverse assignment from the Line type to the Rectangle type is declared with the explicit keyword. Here you need to specify only an explicit cast

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

If you do not specify an explicit cast, the compiler will generate an error.

After running the program gives the following result

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

  

1.4. An example of operator overloading () for types Point<=>Circle. Demonstration of types cast: int<=Point, Circle<=Point, Point<=Circle, double<=Circle

The example converts between Point <=> Circle classes. The following methods are overloaded in the Point class

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

In the Circle class, the following operators are overloaded

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

This example clearly shows the possibilities of overloading the operator () for co-casting between significant standard types (int, double) and native types (Point, Circle).

The text of the demo program is as follows.

using System;

namespace ConsoleApp6
{
  // Class describing a point on a coordinate plane
  class Point
  {
    // Internal class fields
    private double x, y;

    // Constructor
    public Point(double x, double y)
    {
      this.x = x;
      this.y = y;
    }

    // Access properties
    public double X { get { return x; } }
    public double Y { get { return y; } }

    // Method that prints the coordinates of a point
    public void Print(string msg)
    {
      Console.WriteLine(msg + " => ( " + x + "; " + y + ")");
    }

    // Methods that overload the cast operator ()
    // int <= Point: obj1 = obj2
    public static implicit operator int(Point pt)
    {
      return (int)(pt.x + pt.y); // sum of coordinates
    }

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

  // A class that describes a circle
  class Circle
  {
    // Internal fields
    private double x, y, radius;

    // Constructor
    public Circle(double x, double y, double radius)
    {
      this.x = x;
      this.y = y;
      this.radius = radius;
    }

    // Properties for accessing class fields
    public double X { get { return x; } }
    public double Y { get { return y; } }
    public double Radius { get { return radius; } }

    // Method that outputs the internal state of instance
    public void Print(string msg)
    {
      System.Console.WriteLine(msg + " => x = " + x + "; y = " + y + "; radius = " + radius);
    }

    // Methods that overload the () operator
    // Explicit overload: double<=Circle; obj1 = (double)obj2
    public static explicit operator double(Circle cr)
    {
      // Return the area of a circle
      return Math.PI * cr.radius * cr.radius;
    }

    // Implicit overload: Point<=Circle; obj1 = obj2
    public static implicit operator Point(Circle cr)
    {
      return new Point(cr.X, cr.Y);
    }

    // Implicit overload: 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. Declare instances of classes Point, Circle
      Point pt1 = new Point(2, 9);
      Circle cr1 = new Circle(1, 9, 8);

      // 2. Demonstrate cast methods
      // 2.1. int <= Point - implicit
      int t = pt1; // calls Point.operator int(Point)
      Console.WriteLine("t = " + t);

      // 2.2. Circle <= Point - explicit
      Circle cr2;
      cr2 = (Circle)pt1; // calls Point.operator Circle(Point)
      cr2.Print("cr2");

      // 2.3. double <= Circle - explicit
      double x = (double)cr1; // calls Circle.operator double(Circle)
      Console.WriteLine("x = " + x);

      // 2.4. Point <= Circle - implicit
      Point pt2;
      pt2 = cr1; // calls Circle.operator Point(Circle)
      pt2.Print("pt2");

      // 2.5. Circle <= int - implicit
      Circle cr3;
      cr3 = 8; // calls Circle.operator Circle(int)
      cr3.Print("cr3");

      Console.ReadKey();
    }
  }
}

Result

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

  

1.5. Restrictions imposed on the type cast operator ()

The following restrictions apply to the cast operator:

1. If classes form a hierarchy and one of them is derived from the other, then it is impossible to cast between these classes.

2. The cast can only be implemented in one of the types (classes):

  • only in the target type;
  • only in the destination type.

  

2. Overloading true, false operators. Example

Overloading the true and false operators sets a test for true and false for the data type, which is the current class. After overloading, objects of this type (class) can be used in logical expressions and if, while, do-while, for statements.

The true and false operators are overloaded in pairs. The general form of operator overloading is

class ClassName
{
  ...

  public static bool operator true(ClassName obj)
  {
    // Code that returns true if true
    // ...
  }

  public static bool operator false(ClassName obj)
  {
    // Code that returns false if true, otherwise returns true
    // ...
  }
}

Example.

The example declares a class Rectangle, formed on the basis of the coordinates of the upper left (x1; y1) and bottom right corners (x2; y2). The class overloads the true and false operators. The true and false operators determine whether a rectangle is (or isn’t) a square.

using System;

namespace ConsoleApp6
{
  // Class Rectangle
  class Rectangle
  {
    // The internal data is the coordinates of the extreme corners of the rectangle
    private double x1, y1, x2, y2;

    // Constructor
    public Rectangle(double x1, double y1, double x2, double y2)
    {
      this.x1 = x1;
      this.y1 = y1;
      this.x2 = x2;
      this.y2 = y2;
    }

    // Field access properties of the class
    public double X1 { get { return x1; } }
    public double Y1 { get { return y1; } }
    public double X2 { get { return x2; } }
    public double Y2 { get { return y2; } }

    // A method that displays the coordinates of a rectangle
    public void Print(string msg)
    {
      Console.Write(msg + " => ");
      Console.WriteLine("( " + x1 + "; " + y1 + ") - ( " + x2 + "; " + y2 + ")");
    }

    // Operator method that overloads the true operator
    public static bool operator true(Rectangle r)
    {
      return (Math.Abs(r.x1 - r.x2) == Math.Abs(r.y1 - r.y2));
    }

    // Overloading the false operator
    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. Declare 2 instances of the Rectangle class
      Rectangle r1 = new Rectangle(1, 1, 3, 3); // this is a square
      Rectangle r2 = new Rectangle(1, 2, 8, 0); // this is not a square

      // 2. Call the overloaded operator true
      if (r1)
        Console.WriteLine("r1 is square.");
      else
        Console.WriteLine("r1 is not square.");

      // 3. Call the overloaded operator false
      if (r2)
        Console.WriteLine("r2 is square.");
      else
        Console.WriteLine("r2 is not square.");

      Console.ReadKey();
    }
  }
}

Result

r1 is square.
r2 is not square.

  

3. Overloading of logical operators &&, ||. Example

The logical operators && and || themselves are not overloaded. However, they can be modeled in the & and | that can be overloaded. This requires the following conditions to be met:

  • in the class the operators true and false must be overloaded;
  • the logical operators & and | must be overloaded in the class;
  • operator methods (operator&() and operator|()) must return the type of the class in which the overload is performed;
  • parameters of operator methods must be references to the class in which these methods are overloaded.

Example.

The following example shows the overloading of the logical operators &&, ||. The Point3D class is declared, which implements a point in three-dimensional space. In the class are declared:

  • internal hidden (private) fields x, y, z which are coordinates of a point in three-dimensional space;
  • constructor Point3D();
  • properties X, Y, Z of access, respectively, to the fields x, y, z;
  • the operator method operator true(), which overloads the operator true. The method returns true if the point is not at the origin (0; 0; 0);
  • operator method operator false(), which overloads operator false. The method returns false if the point is at the origin;
  • operator method operator|(), which overloads the operator | (logical “OR”). This operator method models the operation of the || operator;
  • operator method operator&(), which overloads operator & (logical “AND”). This operator method models the operation of the && operator;
  • the Print() method that displays the state of the instance (x, y, z coordinates).

 

using System;

namespace ConsoleApp6
{
  // Class Point3D - describes a point in three-dimensional space.
  // The class demonstrates overloading of the && and || operators
  class Point3D
  {
    // Point coordinates
    private double x, y, z;

    // Constructor
    public Point3D(double x, double y, double z)
    {
      this.x = x;
      this.y = y;
      this.z = z;
    }

    // Properties of access to x, y, z.
    public double X { get { return x; } }
    public double Y { get { return y; } }
    public double Z { get { return z; } }

    // Method that overloads the true operator
    public static bool operator true(Point3D pt)
    {
      return !((pt.x == 0) && (pt.y == 0) && (pt.z == 0));
    }

    // Method that overloads the false operator
    public static bool operator false(Point3D pt)
    {
      return (pt.x == 0) && (pt.y == 0) && (pt.z == 0);
    }

    // Method that overloads operator &
    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);
    }

    // Method that overloads operator |
    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. Create 2 instances of the Point3D class
      Point3D pt1 = new Point3D(1, 1, 7);
      Point3D pt2 = new Point3D(0, 0, 4);

      // 2. Invoke the simulated overloaded operators && and ||
      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();
    }
  }
}

Result

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

  


Related topics