Recall: Numerical Conversions

In terms of intrinsic numerical types (sbyte,int,float,etc.), an explicit conversion is required when you attempt to store a larger value in a smaller container, as this could result in a loss of data.

int a = 123;
long b = a; // Implicit conversion from int to long
int c = (int)b; // Explicit conversion from long to int


Recall: Conversions Among Related Class Types

The C# conversion process allows you to cast up and down the class hierarchy.


// 2 related class type
class Base { }
class Derived : Base { }
         
 
// Implicit cast between derived to base.
Base myBaseType;
myBaseType = new Derived();
// Must explicitly cast to store base reference in derived type.
Derived myDerivedType = (Derived)myBaseType;


Creating Custom Conversion Routines

C# provides 2 keywords, explicit and implicit, that you can use to control how your types respond during an attempted conversion.


public struct Rectangle
 {
     public int Width { get; set; }
     public int Height { get; set; }
     public Rectangle(int w,int h)
     {
         Width = w;
         Height = h;
     }
     public void Draw()
     {
         for (int i = 0; i < Height; i++)
         {
             for (int j = 0; j < Width; j++)
             {
                 Console.Write("*");
             }
             Console.WriteLine();
         }
     }
     public override string ToString()
     {
         return $"{Width} {Height}";
     }
 }
 
public struct Square
 {
     public int Length { get; set; }
     public Square(int i) : this()
     {
         Length = i;
     }
     public void Draw()
     {
         for (int i = 0; i < Length; i++)
         {
             for (int j = 0; j < Length; j++)
             {
                 Console.Write("*");
             }
             Console.WriteLine();
         }
     }
     public override string ToString()
     {
         return $"{Length}";
     }
     // Must be defined static
     public static explicit operator Square(Rectangle r)
     {
         Square s = new Square { Length = r.Height };
         return s;
     }
 }

Rectangle r = new Rectangle(15, 4);
Console.WriteLine(r);
r.Draw();
// Conver into a Square, based on the height of the Rectangle.
Square s = (Square)r;
Console.WriteLine(s);
s.Draw();


While it may not be all that helpful to convert a Rectangle into a Square within the same scope, assume you have a function that has been designed to take Square parameters.


// This method requires a Square type
static void DrawSquare(Square sq)
 {
     Console.WriteLine(sq.ToString());
     sq.Draw();
 }

Using your explicit conversion operation on the Square type, you can now pass in Rectangle types for processing using an explicit case, like so:

// Convert Rectangle to Square to invoke method.
Rectangle rect = new Rectangle(10, 5);
Square.DrawSquare((Square)rect);


Additional Explicit Conversions for the Square Type

Given that a square is symmetrical on all sides, it might be helpful to provide an explicit conversion routine that allows the caller to cast from an integer type into a Square (which, of course, will have a side length equal to the incoming integer).


public struct Square
 {
...
public static explicit operator Square(int sideLength)
 {
     Square newSq = new Square { Length = sideLength };
     return newSq;
 }
public static explicit operator int (Square sq)
 {
     return sq.Length;
 }
}
// Converting an into to a Square.
Square sq2 = (Square)80;
Console.WriteLine(sq2);
// Converting a Square to an int
int side = (int)sq2;
Console.WriteLine(side);


To be honest, converting from a Square into an integer may not be the most intuitive operation. However, it does point out an important fact regarding custom conversion routines: the compiler does not care what you convert to or from, if you have written syntactically correct code.


Defining Implicit Conversion Routines

Square s3 = new Square { Length = 85 };
// Attempt to make an implicit cast?
Rectangle rect2 = s3;

This code will not compile, given that you have not provided an implicit conversion routing for the Rectangle type. Now here is the catch: it is illegal to define explicit and implicit conversion functions on the same type if they do not differ by their return type or parameter set. This might seem like a limitation; however, the second catch is that when a type defines an implicit conversion routine, it is legal for the caller to make use of the explicit cast syntax.


public static implicit operator Rectangle(Square s)
 {
     Rectangle r = new Rectangle
     {
         Height = s.Length,
         Width = s.Length * 2 // Assume the Length of the new Rectangle with (Length*2)
     };
     return r;
 }
 
// Implicit cast OK!
Square s3 = new Square { Length = 85 };
Rectangle rect2 = s3;
 
// Explicit syntax still OK!
Square s4 = new Square { Length = 3 };
Rectangle r3 = (Rectangle)s4;
Console.WriteLine(r3);