ここでは複素数を定義する次のような構造体を使って、ユーザー定義の型変換について見ていきます。

struct Complex {
  public double Real; // 実部
  public double Imaginary; // 虚部

  public Complex(double real, double imaginary)
    : this()
  {
    this.Real = real;
    this.Imaginary = imaginary;
  }
}
Structure Complex
  Public Real As Double ' 実部
  Public Imaginary As Double ' 虚部

  Public Sub New(ByVal real As Double, ByVal imaginary As Double)
    MyClass.Real = real
    MyClass.Imaginary = imaginary
  End Sub
End Structure

.NET Framework 4よりSystem.Numerics.Complex構造体が使えるようになっているため、通常このような構造体を独自に定義する必要性はあまりありませんが、ここではユーザー定義の型変換の例として独自の複素数型を取り上げています。 .NET Frameworkで提供されるComplex構造体については複素数型で解説しています。

文字列への変換

ToString

文字列への変換をサポートするためには、ToStringをオーバーライドすることが出来ます。

using System;

struct Complex {
  public double Real; // 実部
  public double Imaginary; // 虚部

  public Complex(double real, double imaginary)
    : this()
  {
    this.Real = real;
    this.Imaginary = imaginary;
  }

  // ToStringのオーバーライド
  public override string ToString()
  {
    return string.Format("({0:F4}, {1:F4})", Real, Imaginary);
  }
}

class Sample {
  static void Main()
  {
    Complex c1 = new Complex(-1.414, 1.414);
    Complex c2 = new Complex( 3.000, 4.000);

    Console.WriteLine("{0} {1}", c1.ToString(), c1);
    Console.WriteLine("{0} {1}", c2.ToString(), c2);
  }
}
Imports System
Imports System.Text

Structure Complex
  Public Real As Double ' 実部
  Public Imaginary As Double ' 虚部

  Public Sub New(ByVal real As Double, ByVal imaginary As Double)
    MyClass.Real = real
    MyClass.Imaginary = imaginary
  End Sub

  ' ToStringのオーバーライド
  Public Overrides Function ToString() As String
    Return string.Format("({0:F4}, {1:F4})", Real, Imaginary)
  End Function
End Structure

Class Sample
  Shared Sub Main()
    Dim c1 As New Complex(-1.414, 1.414)
    Dim c2 As New Complex( 3.000, 4.000)

    Console.WriteLine("{0} {1}", c1.ToString(), c1)
    Console.WriteLine("{0} {1}", c2.ToString(), c2)
  End Sub
End Class
実行結果
(-1.4140, 1.4140) (-1.4140, 1.4140)
(3.0000, 4.0000) (3.0000, 4.0000)

もちろん、次の例ように妥当な理由がある場合は文字列化専用のメソッドを用意しToStringメソッドをオーバーライドしないという方法を採ることもできます。 ただ、すべてのユーザー定義型はObjectを継承していて、ランタイムにより文字列化される際にObject.ToStringメソッドが呼び出されることがあるため、このメソッドをオーバーライドすることで文字列化される際の動作を定義しておいた方がよいでしょう。

using System;

struct Complex {
  public double Real; // 実部
  public double Imaginary; // 虚部

  public Complex(double real, double imaginary)
    : this()
  {
    this.Real = real;
    this.Imaginary = imaginary;
  }

  // 文字列化のためのメソッド
  public string ConvertToString()
  {
    return string.Format("({0:F4}, {1:F4})", Real, Imaginary);
  }
}

class Sample {
  static void Main()
  {
    Complex c1 = new Complex(-1.414, 1.414);
    Complex c2 = new Complex( 3.000, 4.000);

    Console.WriteLine("{0} {1}", c1.ConvertToString(), c1);
    Console.WriteLine("{0} {1}", c2.ConvertToString(), c2);
  }
}
Imports System

Structure Complex
  Public Real As Double ' 実部
  Public Imaginary As Double ' 虚部

  Public Sub New(ByVal real As Double, ByVal imaginary As Double)
    MyClass.Real = real
    MyClass.Imaginary = imaginary
  End Sub

  ' 文字列化のためのメソッド
  Public Function ConvertToString() As String
    Return string.Format("({0:F4}, {1:F4})", Real, Imaginary)
  End Function
End Structure

Class Sample
  Shared Sub Main()
    Dim c1 As New Complex(-1.414, 1.414)
    Dim c2 As New Complex( 3.000, 4.000)

    Console.WriteLine("{0} {1}", c1.ConvertToString(), c1)
    Console.WriteLine("{0} {1}", c2.ConvertToString(), c2)
  End Sub
End Class
実行結果
(-1.4140, 1.4140) Complex
(3.0000, 4.0000) Complex

IFormattable

IFormattableインターフェイスを実装することで、書式を指定した文字列化をサポートすることができます。 また、書式だけでなくカルチャを考慮した文字列化のサポートもIFormattableインターフェイスを用いて実装出来ます。 IFormattableについての詳細は書式の定義と実装で解説しています。

using System;

struct Complex : IFormattable {
  public double Real; // 実部
  public double Imaginary; // 虚部

  public Complex(double real, double imaginary)
    : this()
  {
    this.Real = real;
    this.Imaginary = imaginary;
  }

  // Object.ToStringのオーバーライド
  public override string ToString()
  {
    return ToString(null, null);
  }

  // IFormattable.ToStringの実装
  public string ToString(string format, IFormatProvider formatProvider)
  {
    if (format == "P") {
      // 極座標表示に書式化
      double r = Math.Sqrt(Real * Real + Imaginary * Imaginary);
      double t = Math.Atan2(Imaginary, Real) / Math.PI;

      return string.Format(formatProvider, "{0:F4}∠{1:F4}π", r, t);
    }
    else {
      // ガウス平面座標表示に書式化
      return string.Format(formatProvider, "({0:F4}, {1:F4})", Real, Imaginary);
    }
  }
}

class Sample {
  static void Main()
  {
    Complex c1 = new Complex(-1.414, 1.414);
    Complex c2 = new Complex( 3.000, 4.000);

    Console.WriteLine("{0} {0:P}", c1);
    Console.WriteLine("{0} {0:P}", c2);
  }
}
Imports System

Structure Complex
  Implements IFormattable

  Public Real As Double ' 実部
  Public Imaginary As Double ' 虚部

  Public Sub New(ByVal real As Double, ByVal imaginary As Double)
    MyClass.Real = real
    MyClass.Imaginary = imaginary
  End Sub

  ' Object.ToStringのオーバーライド
  Public Overrides Function ToString() As String
    Return ToString(Nothing, Nothing)
  End Function

  ' IFormattable.ToStringの実装
  Public Function ToString(ByVal format As String, ByVal formatProvider As IFormatProvider) As String Implements IFormattable.ToString
    If format = "P" Then
      ' 極座標表示に書式化
      Dim r As Double = Math.Sqrt(Real * Real + Imaginary * Imaginary)
      Dim t As Double = Math.Atan2(Imaginary, Real) / Math.PI

      Return String.Format(formatProvider, "{0:F4}∠{1:F4}π", r, t)
    Else
      ' ガウス平面座標表示に書式化
      Return String.Format(formatProvider, "({0:F4}, {1:F4})", Real, Imaginary)
    End If
  End Function
End Structure

Class Sample
  Shared Sub Main()
    Dim c1 As New Complex(-1.414, 1.414)
    Dim c2 As New Complex( 3.000, 4.000)

    Console.WriteLine("{0} {0:P}", c1)
    Console.WriteLine("{0} {0:P}", c2)
  End Sub
End Class
実行結果
(-1.4140, 1.4140) 1.9997∠0.7500π
(3.0000, 4.0000) 5.0000∠0.2952π

基本型への変換

ToXXX

他の基本型への変換を定義するもっとも簡単な方法は、文字列化の際と同様、目的の型への変換用のメソッドを用意することです。 次の例では、Complex型をDouble型の値として返すメソッドToDoubleを用意し、複素数の絶対値を返すようにしています。

using System;

struct Complex {
  public double Real; // 実部
  public double Imaginary; // 虚部

  public Complex(double real, double imaginary)
    : this()
  {
    this.Real = real;
    this.Imaginary = imaginary;
  }

  public double ToDouble()
  {
    return Math.Sqrt(Real * Real + Imaginary * Imaginary);
  }
}

class Sample {
  static void Main()
  {
    Complex c1 = new Complex(-1.414, 1.414);
    Complex c2 = new Complex( 3.000, 4.000);

    double d1 = c1.ToDouble();
    double d2 = c2.ToDouble();

    Console.WriteLine(d1);
    Console.WriteLine(d2);
  }
}
Imports System

Structure Complex
  Public Real As Double ' 実部
  Public Imaginary As Double ' 虚部

  Public Sub New(ByVal real As Double, ByVal imaginary As Double)
    MyClass.Real = real
    MyClass.Imaginary = imaginary
  End Sub

  Public Function ToDouble() As Double
    Return Math.Sqrt(Real * Real + Imaginary * Imaginary)
  End Function
End Structure

Class Sample
  Shared Sub Main()
    Dim c1 As New Complex(-1.414, 1.414)
    Dim c2 As New Complex( 3.000, 4.000)

    Dim d1 As Double = c1.ToDouble()
    Dim d2 As Double = c2.ToDouble()

    Console.WriteLine(d1)
    Console.WriteLine(d2)
  End Sub
End Class
実行結果
1.99969797719556
5

ただ、このようなメソッドを用意しても、Convertクラスを使った変換などが出来るようになるわけではありません。 次のコードを実行すると、例外InvalidCastExceptionがスローされます。

using System;

struct Complex {
  public double Real; // 実部
  public double Imaginary; // 虚部

  public Complex(double real, double imaginary)
    : this()
  {
    this.Real = real;
    this.Imaginary = imaginary;
  }

  public double ToDouble()
  {
    return Math.Sqrt(Real * Real + Imaginary * Imaginary);
  }
}

class Sample {
  static void Main()
  {
    Complex c1 = new Complex(-1.414, 1.414);
    Complex c2 = new Complex( 3.000, 4.000);

    double d1 = Convert.ToDouble(c1);
    double d2 = Convert.ToDouble(c2);

    Console.WriteLine(d1);
    Console.WriteLine(d2);
  }
}
Imports System

Structure Complex
  Public Real As Double ' 実部
  Public Imaginary As Double ' 虚部

  Public Sub New(ByVal real As Double, ByVal imaginary As Double)
    MyClass.Real = real
    MyClass.Imaginary = imaginary
  End Sub

  Public Function ToDouble() As Double
    Return Math.Sqrt(Real * Real + Imaginary * Imaginary)
  End Function
End Structure

Class Sample
  Shared Sub Main()
    Dim c1 As New Complex(-1.414, 1.414)
    Dim c2 As New Complex( 3.000, 4.000)

    Dim d1 As Double = Convert.ToDouble(c1)
    Dim d2 As Double = Convert.ToDouble(c2)

    Console.WriteLine(d1)
    Console.WriteLine(d2)
  End Sub
End Class
実行結果
ハンドルされていない例外: System.InvalidCastException: 型 'Complex' のオブジェクトを型 'System.IConvertible' にキャストできません。
   場所 System.Convert.ToDouble(Object value)
   場所 Sample.Main()

IConvertible

Convertクラスを用いた型変換をサポートするには、IConvertibleインターフェイスを実装しなければなりません。 このインターフェイスには、他の基本型への変換を行うためのメソッドが用意されています。 これらのメソッドでは、変換を定義できる場合は変換結果を返し、変換を定義できない場合はInvalidCastExceptionをスローするようにします。

以下はIConvertibleを実装する例です。 この例のComplex型では、IConvertibleを次のように実装しています。

  • Double型への変換では、Complex型の表す複素数の絶対値を返す
  • その他の実数型・整数型への変換では、Complex型の表す複素数の絶対値を目的の型に変換して返す
  • Boolean型の変換では、実部・虚部ともに0の場合はfalse、そうでなければtrueを返す
  • String型への変換では、ガウス平面座標表示に書式化した文字列を返す
  • System.Drawing.PointF型への変換をサポートする
  • それ以外の型への変換はサポートしない(InvalidCastExceptionをスローする)
  • IConvertible.ToString以外は直接呼び出せないように明示的な実装にする
using System;
using System.Drawing;

struct Complex : IConvertible {
  public double Real; // 実部
  public double Imaginary; // 虚部

  public Complex(double real, double imaginary)
    : this()
  {
    this.Real = real;
    this.Imaginary = imaginary;
  }

  TypeCode IConvertible.GetTypeCode()
  {
    // ユーザ定義型であるTypeCode.Objectを返す
    return TypeCode.Object;
  }

  double IConvertible.ToDouble(IFormatProvider provider) {
    return Math.Sqrt(Real * Real + Imaginary * Imaginary);
  }

  float IConvertible.ToSingle(IFormatProvider provider)
  {
    return Convert.ToSingle((this as IConvertible).ToDouble(provider));
  }

  decimal IConvertible.ToDecimal(IFormatProvider provider)
  {
    return Convert.ToDecimal((this as IConvertible).ToDouble(provider));
  }

  byte IConvertible.ToByte(IFormatProvider provider)
  {
    return Convert.ToByte((this as IConvertible).ToDouble(provider));
  }

  short IConvertible.ToInt16(IFormatProvider provider)
  {
    return Convert.ToInt16((this as IConvertible).ToDouble(provider));
  }

  int IConvertible.ToInt32(IFormatProvider provider)
  {
    return Convert.ToInt32((this as IConvertible).ToDouble(provider));
  }

  long IConvertible.ToInt64(IFormatProvider provider)
  {
    return Convert.ToInt64((this as IConvertible).ToDouble(provider));
  }

  sbyte IConvertible.ToSByte(IFormatProvider provider)
  {
    return Convert.ToSByte((this as IConvertible).ToDouble(provider));
  }

  ushort IConvertible.ToUInt16(IFormatProvider provider)
  {
    return Convert.ToUInt16((this as IConvertible).ToDouble(provider));
  }

  uint IConvertible.ToUInt32(IFormatProvider provider)
  {
    return Convert.ToUInt32((this as IConvertible).ToDouble(provider));
  }

  ulong IConvertible.ToUInt64(IFormatProvider provider)
  {
    return Convert.ToUInt64((this as IConvertible).ToDouble(provider));
  }

  bool IConvertible.ToBoolean(IFormatProvider provider)
  {
    return (Real != 0.0 || Imaginary != 0.0);
  }

  public override string ToString()
  {
    // IConvertible.ToString(IFormatProvider)の結果を返す
    return ToString(null);
  }

  public string ToString(IFormatProvider provider)
  {
    return string.Format(provider, "({0:F4}, {1:F4})", Real, Imaginary);
  }

  object IConvertible.ToType(Type conversionType, IFormatProvider provider)
  {
    if (conversionType == typeof(PointF)) {
      return new PointF(Convert.ToSingle(Real), Convert.ToSingle(Imaginary));
    }
    else {
      throw new InvalidCastException();
    }
  }

  char IConvertible.ToChar(IFormatProvider provider)
  {
    throw new InvalidCastException();
  }

  DateTime IConvertible.ToDateTime(IFormatProvider provider)
  {
    throw new InvalidCastException();
  }
}

class Sample {
  static void Main()
  {
    Complex c1 = new Complex(-1.414, 1.414);
    Complex c2 = new Complex( 3.000, 4.000);

    Console.WriteLine("ToString");
    Console.WriteLine("{0} {1} {2}", c1, c1.ToString(), Convert.ToString(c1));
    Console.WriteLine("{0} {1} {2}", c2, c2.ToString(), Convert.ToString(c2));

    double d1 = Convert.ToDouble(c1);
    double d2 = Convert.ToDouble(c2);

    Console.WriteLine("ToDouble");
    Console.WriteLine("{0} => {1}", c1, d1);
    Console.WriteLine("{0} => {1}", c2, d2);

    bool b1 = Convert.ToBoolean(c1);
    bool b2 = Convert.ToBoolean(c2);

    Console.WriteLine("ToBoolean");
    Console.WriteLine("{0} => {1}", c1, b1);
    Console.WriteLine("{0} => {1}", c2, b2);

    int i1 = Convert.ToInt32(c1);
    int i2 = Convert.ToInt32(c2);

    Console.WriteLine("ToInt32");
    Console.WriteLine("{0} => {1}", c1, i1);
    Console.WriteLine("{0} => {1}", c2, i2);

    float s1 = (float)Convert.ChangeType(c1, TypeCode.Single);
    float s2 = (float)Convert.ChangeType(c2, TypeCode.Single);

    Console.WriteLine("ChangeType TypeCode.Single");
    Console.WriteLine("{0} => {1}", c1, s1);
    Console.WriteLine("{0} => {1}", c2, s2);

    PointF p1 = (PointF)Convert.ChangeType(c1, typeof(PointF));
    PointF p2 = (PointF)Convert.ChangeType(c2, typeof(PointF));

    Console.WriteLine("ChangeType PointF");
    Console.WriteLine("{0} => {1}", c1, p1);
    Console.WriteLine("{0} => {1}", c2, p2);

    Console.WriteLine("ToDateTime");
    Console.WriteLine("{0} => {1}", c1, Convert.ToDateTime(c1));
  }
}
Imports System
Imports System.Drawing

Structure Complex
  Implements IConvertible

  Public Real As Double ' 実部
  Public Imaginary As Double ' 虚部

  Public Sub New(ByVal real As Double, ByVal imaginary As Double)
    MyClass.Real = real
    MyClass.Imaginary = imaginary
  End Sub

  Private Function GetTypeCode() As TypeCode Implements IConvertible.GetTypeCode
    ' ユーザ定義型であるTypeCode.Objectを返す
    Return TypeCode.Object
  End Function

  Private Function ToDouble(ByVal provider As IFormatProvider) As Double Implements IConvertible.ToDouble
    Return Math.Sqrt(Real * Real + Imaginary * Imaginary)
  End Function

  Private Function ToSingle(ByVal provider As IFormatProvider) As Single Implements IConvertible.ToSingle
    Return Convert.ToSingle(DirectCast(Me, IConvertible).ToDouble(provider))
  End Function

  Private Function ToDecimal(ByVal provider As IFormatProvider) As Decimal Implements IConvertible.ToDecimal
    Return Convert.ToDecimal(DirectCast(Me, IConvertible).ToDouble(provider))
  End Function

  Private Function ToByte(ByVal provider As IFormatProvider) As Byte Implements IConvertible.ToByte
    Return Convert.ToByte(DirectCast(Me, IConvertible).ToDouble(provider))
  End Function

  Private Function ToInt16(ByVal provider As IFormatProvider) As Short Implements IConvertible.ToInt16
    Return Convert.ToInt16(DirectCast(Me, IConvertible).ToDouble(provider))
  End Function

  Private Function ToInt32(ByVal provider As IFormatProvider) As Integer Implements IConvertible.ToInt32
    Return Convert.ToInt32(DirectCast(Me, IConvertible).ToDouble(provider))
  End Function

  Private Function ToInt64(ByVal provider As IFormatProvider) As Long Implements IConvertible.ToInt64
    Return Convert.ToInt64(DirectCast(Me, IConvertible).ToDouble(provider))
  End Function

  Private Function ToSByte(ByVal provider As IFormatProvider) As SByte Implements IConvertible.ToSByte
    Return Convert.ToSByte(DirectCast(Me, IConvertible).ToDouble(provider))
  End Function

  Private Function ToUInt16(ByVal provider As IFormatProvider) As UShort Implements IConvertible.ToUInt16
    Return Convert.ToUInt16(DirectCast(Me, IConvertible).ToDouble(provider))
  End Function

  Private Function ToUInt32(ByVal provider As IFormatProvider) As UInteger Implements IConvertible.ToUInt32
    Return Convert.ToUInt32(DirectCast(Me, IConvertible).ToDouble(provider))
  End Function

  Private Function ToUInt64(ByVal provider As IFormatProvider) As ULong Implements IConvertible.ToUInt64
    Return Convert.ToUInt64(DirectCast(Me, IConvertible).ToDouble(provider))
  End Function

  Private Function ToBoolean(ByVal provider As IFormatProvider) As Boolean Implements IConvertible.ToBoolean
    Return (Real <> 0.0 OrElse Imaginary <> 0.0)
  End Function

  Public Overloads Overrides Function ToString() As String
    ' IConvertible.ToString(IFormatProvider)の結果を返す
    Return ToString(Nothing)
  End Function

  Public Overloads Function ToString(ByVal provider As IFormatProvider) As String Implements IConvertible.ToString
    Return String.Format(provider, "({0:F4}, {1:F4})", Real, Imaginary)
  End Function

  Private Function ToType(ByVal conversionType As Type, ByVal provider As IFormatProvider) As Object Implements IConvertible.ToType
    If conversionType Is GetType(PointF) Then
      Return New PointF(Convert.ToSingle(Real), Convert.ToSingle(Imaginary))
    Else
      Throw New InvalidCastException()
    End If
  End Function

  Private Function ToChar(ByVal provider As IFormatProvider) As Char Implements IConvertible.ToChar
    Throw New InvalidCastException()
  End Function

  Private Function ToDateTime(ByVal provider As IFormatProvider) As DateTime Implements IConvertible.ToDateTime
    Throw New InvalidCastException()
  End Function
End Structure

Class Sample
  Shared Sub Main()
    Dim c1 As New Complex(-1.414, 1.414)
    Dim c2 As New Complex( 3.000, 4.000)

    Console.WriteLine("ToString")
    Console.WriteLine("{0} {1} {2}", c1, c1.ToString(), Convert.ToString(c1))
    Console.WriteLine("{0} {1} {2}", c2, c2.ToString(), Convert.ToString(c2))

    Dim d1 As Double = Convert.ToDouble(c1)
    Dim d2 As Double = Convert.ToDouble(c2)

    Console.WriteLine("ToDouble")
    Console.WriteLine("{0} => {1}", c1, d1)
    Console.WriteLine("{0} => {1}", c2, d2)

    Dim b1 As Boolean = Convert.ToBoolean(c1)
    Dim b2 As Boolean = Convert.ToBoolean(c2)

    Console.WriteLine("ToBoolean")
    Console.WriteLine("{0} => {1}", c1, b1)
    Console.WriteLine("{0} => {1}", c2, b2)

    Dim i1 As Integer = Convert.ToInt32(c1)
    Dim i2 As Integer = Convert.ToInt32(c2)

    Console.WriteLine("ToInt32")
    Console.WriteLine("{0} => {1}", c1, i1)
    Console.WriteLine("{0} => {1}", c2, i2)

    Dim s1 As Single = DirectCast(Convert.ChangeType(c1, TypeCode.Single), Single)
    Dim s2 As Single = DirectCast(Convert.ChangeType(c2, TypeCode.Single), Single)

    Console.WriteLine("ChangeType TypeCode.Single")
    Console.WriteLine("{0} => {1}", c1, s1)
    Console.WriteLine("{0} => {1}", c2, s2)

    Dim p1 As PointF = DirectCast(Convert.ChangeType(c1, GetType(PointF)), PointF)
    Dim p2 As PointF = DirectCast(Convert.ChangeType(c2, GetType(PointF)), PointF)

    Console.WriteLine("ChangeType PointF")
    Console.WriteLine("{0} => {1}", c1, p1)
    Console.WriteLine("{0} => {1}", c2, p2)

    Console.WriteLine("ToDateTime")
    Console.WriteLine("{0} => {1}", c1, Convert.ToDateTime(c1))
  End Sub
End Class
実行結果
ToString
(-1.4140, 1.4140) (-1.4140, 1.4140) (-1.4140, 1.4140)
(3.0000, 4.0000) (3.0000, 4.0000) (3.0000, 4.0000)
ToDouble
(-1.4140, 1.4140) => 1.99969797719556
(3.0000, 4.0000) => 5
ToBoolean
(-1.4140, 1.4140) => True
(3.0000, 4.0000) => True
ToInt32
(-1.4140, 1.4140) => 2
(3.0000, 4.0000) => 5
ChangeType TypeCode.Single
(-1.4140, 1.4140) => 1.999698
(3.0000, 4.0000) => 5
ChangeType PointF
(-1.4140, 1.4140) => {X=-1.414, Y=1.414}
(3.0000, 4.0000) => {X=3, Y=4}
ToDateTime

ハンドルされていない例外: System.InvalidCastException: 指定されたキャストは有効ではありません。
   場所 Complex.System.IConvertible.ToDateTime(IFormatProvider provider)
   場所 System.Convert.ToDateTime(Object value)
   場所 Sample.Main()

暗黙的・明示的な型変換

ここまでではメソッドやインターフェイスを通した型変換を行う例を解説してきましたが、型変換演算子をオーバーロード(多重定義)することで型変換演算子(キャスト演算子、CType)を用いた型変換をサポート出来るようになります。

暗黙的な型変換(拡大変換)演算子のオーバーロード

C#ではimplicit operator、VBではWidening Operator CTypeを用いることで暗黙の型変換演算子をオーバーロード出来ます。 この演算子は、拡大変換を定義する場合にオーバーロードします。 変換先の型は必ずしも基本型である必要は無く、変換が定義できるならどのような型への変換も実装出来ます。 また、変換先の型を複数定義することも出来ます。

次の例は、Complex型を暗黙的にDouble型およびString型へと変換できるよう演算子をオーバーロードする例です。

using System;

struct Complex {
  public double Real; // 実部
  public double Imaginary; // 虚部

  public Complex(double real, double imaginary)
    : this()
  {
    this.Real = real;
    this.Imaginary = imaginary;
  }

  public override string ToString()
  {
    return string.Format("({0:F4}, {1:F4})", Real, Imaginary);
  }

  // doubleへの型変換演算子
  public static implicit operator double(Complex c)
  {
    return Math.Sqrt(c.Real * c.Real + c.Imaginary * c.Imaginary);
  }

  // stringへの型変換演算子
  public static implicit operator string(Complex c)
  {
    return c.ToString();
  }
}

class Sample {
  static void Main()
  {
    Complex c1 = new Complex(-1.414, 1.414);
    Complex c2 = new Complex( 3.000, 4.000);

    double d1 = c1;
    double d2 = c2;

    Console.WriteLine("{0} => {1}", c1, d1);
    Console.WriteLine("{0} => {1}", c2, d2);

    string s1 = c1;
    string s2 = c2;

    Console.WriteLine("{0} => {1}", c1, s1);
    Console.WriteLine("{0} => {1}", c2, s2);
  }
}
Imports System

Structure Complex
  Public Real As Double ' 実部
  Public Imaginary As Double ' 虚部

  Public Sub New(ByVal real As Double, ByVal imaginary As Double)
    MyClass.Real = real
    MyClass.Imaginary = imaginary
  End Sub

  Public Overrides Function ToString() As String
    Return String.Format("({0:F4}, {1:F4})", Real, Imaginary)
  End Function

  ' Doubleへの型変換演算子
  Public Shared Widening Operator CType(ByVal c As Complex) As Double
    Return Math.Sqrt(c.Real * c.Real + c.Imaginary * c.Imaginary)
  End Operator

  ' Stringへの型変換演算子
  Public Shared Widening Operator CType(ByVal c As Complex) As String
    Return c.ToString()
  End Operator
End Structure

Class Sample
  Shared Sub Main()
    Dim c1 As New Complex(-1.414, 1.414)
    Dim c2 As New Complex( 3.000, 4.000)

    Dim d1 As Double = c1
    Dim d2 As Double = c2

    Console.WriteLine("{0} => {1}", c1, d1)
    Console.WriteLine("{0} => {1}", c2, d2)

    Dim s1 As String = c1
    Dim s2 As String = c2

    Console.WriteLine("{0} => {1}", c1, s1)
    Console.WriteLine("{0} => {1}", c2, s2)
  End Sub
End Class
実行結果
(-1.4140, 1.4140) => 1.99969797719556
(3.0000, 4.0000) => 5
(-1.4140, 1.4140) => (-1.4140, 1.4140)
(3.0000, 4.0000) => (3.0000, 4.0000)

型変換演算子となるメソッドはpublic static/Public Sharedでなければなりません。 また、暗黙の型変換演算子のオーバーロードする際にはいくつかの注意が必要です。

暗黙の型変換演算子がオーバーロードされている場合、異なる型の変数への意図しない代入でも変換が行われてしまうことになります。 上記の例では、Complex型から暗黙的にString型へ変換することが出来るようになっていますが、これは他の多くの基本型とは異なる動作であるため、混乱を招く可能性があります。 このような場合の他、変換によりデータの欠損や桁落ち、オーバーフローが発生する場合は、代わりに明示的な型変換演算子を用いるべきです。 言い換えると、暗黙の変換が行われても予期しない結果となることもなく、変換により例外が発生しないような場合には、暗黙の型変換演算子をオーバーロードしても問題にはならないと言えます。

明示的な型変換(縮小変換)演算子のオーバーロード

C#ではexplicit operator、VBではNarrowing Operator CTypeを用いることで明示的な型変換演算子をオーバーロード出来ます。 この演算子は、縮小変換を定義する場合にオーバーロードします。 暗黙的な型変換演算子と同様、変換先の型は必ずしも基本型である必要は無く、変換が定義できるならどのような型への変換も実装出来ます。 また、変換先の型を複数定義することも出来ます。

次の例は、Complex型を明示的にInteger型およびPointF型へと変換できるよう演算子をオーバーロードする例です。

using System;
using System.Drawing;

struct Complex {
  public double Real; // 実部
  public double Imaginary; // 虚部

  public Complex(double real, double imaginary)
    : this()
  {
    this.Real = real;
    this.Imaginary = imaginary;
  }

  public override string ToString()
  {
    return string.Format("({0:F4}, {1:F4})", Real, Imaginary);
  }

  public static explicit operator int(Complex c)
  {
    // 実部・虚部の値が非数もしくは無限大の場合は、OverflowExceptionをスローする
    if (double.IsNaN(c.Real) || double.IsNaN(c.Imaginary) ||
        double.IsInfinity(c.Real) || double.IsInfinity(c.Imaginary))
      throw new OverflowException("NaN or Infinity");

    return Convert.ToInt32(Math.Sqrt(c.Real * c.Real + c.Imaginary * c.Imaginary));
  }

  public static explicit operator PointF(Complex c)
  {
    return new PointF(Convert.ToSingle(c.Real), Convert.ToSingle(c.Imaginary));
  }
}

class Sample {
  static void Main()
  {
    Complex c1 = new Complex(-1.414, 1.414);
    Complex c2 = new Complex( 3.000, 4.000);

    int i1 = (int)c1;
    int i2 = (int)c2;

    Console.WriteLine("{0} => {1}", c1, i1);
    Console.WriteLine("{0} => {1}", c2, i2);

    PointF p1 = (PointF)c1;
    PointF p2 = (PointF)c2;

    Console.WriteLine("{0} => {1}", c1, p1);
    Console.WriteLine("{0} => {1}", c2, p2);

    Complex c3 = new Complex(double.NaN, double.NegativeInfinity);

    Console.WriteLine("{0} => {1}", c3, (PointF)c3);
    Console.WriteLine("{0} => {1}", c3, (int)c3);
  }
}
Imports System
Imports System.Drawing

Structure Complex
  Public Real As Double ' 実部
  Public Imaginary As Double ' 虚部

  Public Sub New(ByVal real As Double, ByVal imaginary As Double)
    MyClass.Real = real
    MyClass.Imaginary = imaginary
  End Sub

  Public Overrides Function ToString() As String
    Return String.Format("({0:F4}, {1:F4})", Real, Imaginary)
  End Function

  Public Shared Narrowing Operator CType(ByVal c As Complex) As Integer
    ' 実部・虚部の値が非数もしくは無限大の場合は、OverflowExceptionをスローする
    If Double.IsNaN(c.Real) OrElse Double.IsNaN(c.Imaginary) OrElse _
       Double.IsInfinity(c.Real) OrElse Double.IsInfinity(c.Imaginary) Then
      Throw New OverflowException("NaN or Infinity")
    Else
      Return Convert.ToInt32(Math.Sqrt(c.Real * c.Real + c.Imaginary * c.Imaginary))
    End If
  End Operator

  Public Shared Narrowing Operator CType(ByVal c As Complex) As PointF
    Return New PointF(Convert.ToSingle(c.Real), Convert.ToSingle(c.Imaginary))
  End Operator
End Structure

Class Sample
  Shared Sub Main()
    Dim c1 As New Complex(-1.414, 1.414)
    Dim c2 As New Complex( 3.000, 4.000)

    Dim i1 As Integer = CInt(c1)
    Dim i2 As Integer = CType(c2, Integer)

    Console.WriteLine("{0} => {1}", c1, i1)
    Console.WriteLine("{0} => {1}", c2, i2)

    Dim p1 As PointF = CType(c1, PointF)
    Dim p2 As PointF = CType(c2, PointF)

    Console.WriteLine("{0} => {1}", c1, p1)
    Console.WriteLine("{0} => {1}", c2, p2)

    Dim c3 As New Complex(Double.NaN, Double.NegativeInfinity)

    Console.WriteLine("{0} => {1}", c3, CType(c3, PointF))
    Console.WriteLine("{0} => {1}", c3, CType(c3, Integer))
  End Sub
End Class
実行結果
(-1.4140, 1.4140) => 2
(3.0000, 4.0000) => 5
(-1.4140, 1.4140) => {X=-1.414, Y=1.414}
(3.0000, 4.0000) => {X=3, Y=4}
(NaN (非数値), -∞) => {X=NaN (非数値), Y=-∞}

ハンドルされていない例外: System.OverflowException: NaN or Infinity
   場所 Complex.op_Explicit(Complex c)
   場所 Sample.Main()

暗黙的な型変換演算子と同様、明示的な型変換演算子となるメソッドはpublic static/Public Sharedでなければなりません。 また、暗黙的な型変換演算子とは異なり、変換に際してOverflowExceptionInvalidCastExceptionFormatExceptionなどの例外をスローすることが出来ます。

上記の例では、非数もしくは無限大の値を持つ場合、Integer型への変換時にOverflowExceptionをスローするようにしていますが、値が変換できない形式ならFormatException、定義できない変換であればInvalidCastExceptionをスローするように出来ます。

相互変換が可能な型でのオーバーロード

二つの型で型変換演算子をオーバーロードすることで、型を相互に変換できるようにすることが出来ます。 以下の例では、直交形式と極形式の二つの形式で複素数を表現する型を用意し、相互に変換できるよう演算子をオーバーロードしています。

using System;

struct CartesianComplex {
  public double Real; // 実部
  public double Imaginary; // 虚部

  public CartesianComplex(double real, double imaginary)
    : this()
  {
    this.Real = real;
    this.Imaginary = imaginary;
  }

  public override string ToString()
  {
    return string.Format("({0:F4}, {1:F4})", Real, Imaginary);
  }

  // PolarComplexへの型変換
  public static explicit operator PolarComplex(CartesianComplex c)
  {
    return new PolarComplex(Math.Sqrt(c.Real * c.Real + c.Imaginary * c.Imaginary),
                            Math.Atan2(c.Imaginary, c.Real));
  }
}

struct PolarComplex {
  public double Magnitude; // 絶対値
  public double Phase; // 偏角

  public PolarComplex(double magnitude, double phase)
    : this()
  {
    this.Magnitude = magnitude;
    this.Phase = phase;
  }

  public override string ToString()
  {
    return string.Format("{0:F4}∠{1:F4}π", Magnitude, Phase / Math.PI);
  }

  // CartesianComplexへの型変換
  public static explicit operator CartesianComplex(PolarComplex c)
  {
    return new CartesianComplex(c.Magnitude * Math.Cos(c.Phase),
                                c.Magnitude * Math.Sin(c.Phase));
  }
}

class Sample {
  static void Main()
  {
    CartesianComplex c1 = new CartesianComplex(-1.414, 1.414);
    CartesianComplex c2 = new CartesianComplex( 3.000, 4.000);

    PolarComplex p1 = (PolarComplex)c1;
    PolarComplex p2 = (PolarComplex)c2;

    Console.WriteLine("{0} => {1}", c1, p1);
    Console.WriteLine("{0} => {1}", c2, p2);

    c1 = (CartesianComplex)p1;
    c2 = (CartesianComplex)p2;

    Console.WriteLine("{0} => {1}", p1, c1);
    Console.WriteLine("{0} => {1}", p2, c2);
  }
}
Imports System

Structure CartesianComplex
  Public Real As Double ' 実部
  Public Imaginary As Double ' 虚部

  Public Sub New(ByVal real As Double, ByVal imaginary As Double)
    MyClass.Real = real
    MyClass.Imaginary = imaginary
  End Sub

  Public Overrides Function ToString() As String
    Return String.Format("({0:F4}, {1:F4})", Real, Imaginary)
  End Function

  ' PolarComplexへの型変換
  Public Shared Narrowing Operator CType(ByVal c As CartesianComplex) As PolarComplex
    Return New PolarComplex(Math.Sqrt(c.Real * c.Real + c.Imaginary * c.Imaginary), _
                            Math.Atan2(c.Imaginary, c.Real))
  End Operator
End Structure

Structure PolarComplex
  Public Magnitude As Double ' 絶対値
  Public Phase As Double ' 偏角

  Public Sub New(ByVal magnitude As Double, ByVal phase As Double)
    MyClass.Magnitude = magnitude
    MyClass.Phase = phase
  End Sub

  Public Overrides Function ToString() As String
    Return String.Format("{0:F4}∠{1:F4}π", Magnitude, Phase / Math.PI)
  End Function

  ' CartesianComplexへの型変換
  Public Shared Narrowing Operator CType(ByVal c As PolarComplex) As CartesianComplex
    Return New CartesianComplex(c.Magnitude * Math.Cos(c.Phase), _
                                c.Magnitude * Math.Sin(c.Phase))
  End Operator
End Structure

Class Sample
  Shared Sub Main()
    Dim c1 As New CartesianComplex(-1.414, 1.414)
    Dim c2 As New CartesianComplex( 3.000, 4.000)

    Dim p1 As PolarComplex = CType(c1, PolarComplex)
    Dim p2 As PolarComplex = CType(c2, PolarComplex)

    Console.WriteLine("{0} => {1}", c1, p1)
    Console.WriteLine("{0} => {1}", c2, p2)

    c1 = CType(p1, CartesianComplex)
    c2 = CType(p2, CartesianComplex)

    Console.WriteLine("{0} => {1}", p1, c1)
    Console.WriteLine("{0} => {1}", p2, c2)
  End Sub
End Class
実行結果
(-1.4140, 1.4140) => 1.9997∠0.7500π
(3.0000, 4.0000) => 5.0000∠0.2952π
1.9997∠0.7500π => (-1.4140, 1.4140)
5.0000∠0.2952π => (3.0000, 4.0000)

この例では、CartesianComplexからPolarComplexへの変換はCartesianComplexで、PolarComplexからCartesianComplexへの変換はPolarComplexで定義していますが、型変換演算子をオーバーロードする場合は必ずしも変換元の型に実装しなければならないというわけではなく、変換前・変換先の型ならどちらでも定義することが出来ます。 次の例では、先の例での型変換演算子の両方をPolarComplexでオーバーロードしています。

using System;

struct CartesianComplex {
  public double Real; // 実部
  public double Imaginary; // 虚部

  public CartesianComplex(double real, double imaginary)
    : this()
  {
    this.Real = real;
    this.Imaginary = imaginary;
  }

  public override string ToString()
  {
    return string.Format("({0:F4}, {1:F4})", Real, Imaginary);
  }
}

struct PolarComplex {
  public double Magnitude; // 絶対値
  public double Phase; // 偏角

  public PolarComplex(double magnitude, double phase)
    : this()
  {
    this.Magnitude = magnitude;
    this.Phase = phase;
  }

  public override string ToString()
  {
    return string.Format("{0:F4}∠{1:F4}π", Magnitude, Phase / Math.PI);
  }

  // CartesianComplexからPolarComplexへの型変換
  public static explicit operator PolarComplex(CartesianComplex c)
  {
    return new PolarComplex(Math.Sqrt(c.Real * c.Real + c.Imaginary * c.Imaginary),
                            Math.Atan2(c.Imaginary, c.Real));
  }

  // PolarComplexからCartesianComplexへの型変換
  public static explicit operator CartesianComplex(PolarComplex c)
  {
    return new CartesianComplex(c.Magnitude * Math.Cos(c.Phase),
                                c.Magnitude * Math.Sin(c.Phase));
  }
}

class Sample {
  static void Main()
  {
    CartesianComplex c1 = new CartesianComplex(-1.414, 1.414);
    CartesianComplex c2 = new CartesianComplex( 3.000, 4.000);

    PolarComplex p1 = (PolarComplex)c1;
    PolarComplex p2 = (PolarComplex)c2;

    Console.WriteLine("{0} => {1}", c1, p1);
    Console.WriteLine("{0} => {1}", c2, p2);

    c1 = (CartesianComplex)p1;
    c2 = (CartesianComplex)p2;

    Console.WriteLine("{0} => {1}", p1, c1);
    Console.WriteLine("{0} => {1}", p2, c2);
  }
}
Imports System

Structure CartesianComplex
  Public Real As Double ' 実部
  Public Imaginary As Double ' 虚部

  Public Sub New(ByVal real As Double, ByVal imaginary As Double)
    MyClass.Real = real
    MyClass.Imaginary = imaginary
  End Sub

  Public Overrides Function ToString() As String
    Return String.Format("({0:F4}, {1:F4})", Real, Imaginary)
  End Function
End Structure

Structure PolarComplex
  Public Magnitude As Double ' 絶対値
  Public Phase As Double ' 偏角

  Public Sub New(ByVal magnitude As Double, ByVal phase As Double)
    MyClass.Magnitude = magnitude
    MyClass.Phase = phase
  End Sub

  Public Overrides Function ToString() As String
    Return String.Format("{0:F4}∠{1:F4}π", Magnitude, Phase / Math.PI)
  End Function

  ' CartesianComplexからPolarComplexへの型変換
  Public Shared Narrowing Operator CType(ByVal c As CartesianComplex) As PolarComplex
    Return New PolarComplex(Math.Sqrt(c.Real * c.Real + c.Imaginary * c.Imaginary), _
                            Math.Atan2(c.Imaginary, c.Real))
  End Operator

  ' PolarComplexからCartesianComplexへの型変換
  Public Shared Narrowing Operator CType(ByVal c As PolarComplex) As CartesianComplex
    Return New CartesianComplex(c.Magnitude * Math.Cos(c.Phase), _
                                c.Magnitude * Math.Sin(c.Phase))
  End Operator
End Structure

Class Sample
  Shared Sub Main()
    Dim c1 As New CartesianComplex(-1.414, 1.414)
    Dim c2 As New CartesianComplex( 3.000, 4.000)

    Dim p1 As PolarComplex = CType(c1, PolarComplex)
    Dim p2 As PolarComplex = CType(c2, PolarComplex)

    Console.WriteLine("{0} => {1}", c1, p1)
    Console.WriteLine("{0} => {1}", c2, p2)

    c1 = CType(p1, CartesianComplex)
    c2 = CType(p2, CartesianComplex)

    Console.WriteLine("{0} => {1}", p1, c1)
    Console.WriteLine("{0} => {1}", p2, c2)
  End Sub
End Class
実行結果
(-1.4140, 1.4140) => 1.9997∠0.7500π
(3.0000, 4.0000) => 5.0000∠0.2952π
1.9997∠0.7500π => (-1.4140, 1.4140)
5.0000∠0.2952π => (3.0000, 4.0000)

当然ながら、型変換演算子からは他の型の非パブリックなメンバにはアクセス出来ないので、この例の様な方法が採れる場合は多くはありません。

その他

Converter<TInput, TOutput>

Converterジェネリックデリゲートは任意の型変換を行うメソッドを表すデリゲートです。 このデリゲートは、Array.ConvertAllList.ConvertAllなどのメソッドで、特定の型から別の型に変換する場合に使われます。 型パラメータTInputとTOutputで変換前と変換後の型を任意に指定することが出来ます。 このデリゲートと変換を行うメソッドを組み合わせて使うことで、型変換演算子が定義されていない・Convertクラスで変換できない場合や異なる変換ルールを使用したい場合に、独自に変換処理を定義して型の変換を行えるようになります。

次の例では、Converterデリゲートを使って複素数Complex型の配列をPoint型の配列に変換しています。

using System;
using System.Drawing;

struct Complex {
  public double Real; // 実部
  public double Imaginary; // 虚部

  public Complex(double real, double imaginary)
    : this()
  {
    this.Real = real;
    this.Imaginary = imaginary;
  }

  public override string ToString()
  {
    return string.Format("({0:F4}, {1:F4})", Real, Imaginary);
  }
}

class Sample {
  // ComplexをPointに変換するメソッド
  static Point ConvertToPoint(Complex c)
  {
    return new Point(Convert.ToInt32(c.Real), Convert.ToInt32(c.Imaginary));
  }

  static void Main()
  {
    Complex[] cxs = new[] {
      new Complex(-1.414, 1.414),
      new Complex( 3.000, 4.000),
      new Complex( 0.000, 0.000),
    };

    foreach (Complex e in cxs) {
      Console.WriteLine(e);
    }

    Point[] pts = Array.ConvertAll(cxs, ConvertToPoint);

    foreach (Point e in pts) {
      Console.WriteLine(e);
    }
  }
}
Imports System
Imports System.Drawing

Structure Complex
  Public Real As Double ' 実部
  Public Imaginary As Double ' 虚部

  Public Sub New(ByVal real As Double, ByVal imaginary As Double)
    MyClass.Real = real
    MyClass.Imaginary = imaginary
  End Sub

  Public Overrides Function ToString() As String
    Return String.Format("({0:F4}, {1:F4})", Real, Imaginary)
  End Function
End Structure

Class Sample
  ' ComplexをPointに変換するメソッド
  Shared Function ConvertToPoint(ByVal c As Complex) As Point
    Return New Point(Convert.ToInt32(c.Real), Convert.ToInt32(c.Imaginary))
  End Function

  Shared Sub Main()
    Dim cxs() As Complex = New Complex() { _
      New Complex(-1.414, 1.414), _
      New Complex( 3.000, 4.000), _
      New Complex( 0.000, 0.000) _
    }

    For Each c As Complex In cxs
      Console.WriteLine(c)
    Next

    Dim pts() As Point = Array.ConvertAll(cxs, AddressOf ConvertToPoint)

    For Each p As Point In pts
      Console.WriteLine(p)
    Next
  End Sub
End Class
実行結果
(-1.4140, 1.4140)
(3.0000, 4.0000)
(0.0000, 0.0000)
{X=-1,Y=1}
{X=3,Y=4}
{X=0,Y=0}