[.NET] クラスや構造体を作る時におすすめのインターフェース（数値型編）

こんにちはー！ニアです。
今回は.NETでクラスや構造体を作る時に、継承して実装すると便利なインターフェースを紹介していきます。

ここでは、数値型の構造体を例として取り上げます。

#1. 数値系の構造体の場合

例えば、[System.Int32](https://msdn.microsoft.com/ja-jp/library/system.int32.aspx)や[System.Double](https://msdn.microsoft.com/ja-jp/library/system.double.aspx)などの構造体は、以下の5つのインターフェースを実装しています。

* **IComparable**
* **IFormattable**
* **IConvertible**
* **IComparable\<T\>**
* **IEquatable\<T\>**

```csharp:System.Int32
[SerializableAttribute]
[ComVisibleAttribute(true)]
public struct Int32 : IComparable, IFormattable, IConvertible, 
	IComparable<int>, IEquatable<int>
```

```csharp:System.Double
[SerializableAttribute]
[ComVisibleAttribute(true)]
public struct Double : IComparable, IFormattable, IConvertible, 
	IComparable<double>, IEquatable<double>
```

ここからは、その5つのインターフェースの概要と、それらを実装することでできることを紹介していきます。

##1.1. IComparableインターフェース、IComparable\<T\>インターフェース

###概要

[IComparable](https://msdn.microsoft.com/ja-jp/library/system.icomparable.aspx)インターフェースは、2つのインスタンスの大小を比較した結果を返す[CompareTo](https://msdn.microsoft.com/ja-jp/library/system.icomparable.compareto.aspx)メソッドを持ちます。

```csharp:IComparable
[ComVisibleAttribute(true)]
public interface IComparable {
	int CompareTo( object obj );
}
```

[IComparable\<T\>](https://msdn.microsoft.com/ja-jp/library/4d7sx9hd.aspx)インターフェースは、IComparableインターフェースのジェネリクス版です。

```csharp:IComparable(T)
[ComVisibleAttribute(true)]
public interface IComparable<in T> {
	int CompareTo( T other );
}
```

###これらのインターフェースを継承して実装することでできること

* **配列やリストのソート（[Array.Sort](https://msdn.microsoft.com/ja-jp/library/system.array.sort.aspx) / [List\<T\>.Sort](https://msdn.microsoft.com/ja-jp/library/3da4abas.aspx)）**
* **LINQにおける並び替え（[Enumerable.OrderBy](https://msdn.microsoft.com/ja-jp/library/system.linq.enumerable.orderby.aspx) / [Enumerable.OrderByDecending](https://msdn.microsoft.com/ja-jp/library/system.linq.enumerable.orderbydescending.aspx) / [Enumerable.ThenBy](https://msdn.microsoft.com/ja-jp/library/system.linq.enumerable.thenby.aspx) / [Enumerable.ThenByDecending](https://msdn.microsoft.com/ja-jp/library/system.linq.enumerable.thenbydescending.aspx)）**
* **ソート済みのコレクション（[SortedSet](https://msdn.microsoft.com/ja-jp/library/dd412070.aspx) / [SortedList](https://msdn.microsoft.com/ja-jp/library/ms132319.aspx)（キーで使用） / [SortedDictionary](https://msdn.microsoft.com/ja-jp/library/f7fta44c.aspx)（キーで使用））**


なお、IComparable.CompareToとIComparable\<T\>.CompareToの両方を継承した場合、**IComparable\<T\>.CompareTo**を優先して呼び出されます。

##1.2. IFormattableインターフェース

###概要

[IFormattable](https://msdn.microsoft.com/ja-jp/library/system.iformattable.aspx)インターフェースは、書式を指定してインスタンスの値を文字列に変換する[ToString(String, IFormatProvider)](https://msdn.microsoft.com/ja-jp/library/system.iformattable.tostring.aspx)メソッドを持ちます。

```csharp:IFormattable
[ComVisibleAttribute(true)]
public interface IFormattable {
	string ToString( string format, IFormatProvider provider );
}
```

###このインターフェースを継承して実装することでできること

* **書式を指定して、コンソールやストリームへの書き出し（[Console.Write](https://msdn.microsoft.com/ja-jp/library/system.console.write.aspx) / [Console.WriteLine](https://msdn.microsoft.com/ja-jp/library/system.console.writeline.aspx)など）**
* **[String.Format](https://msdn.microsoft.com/ja-jp/library/system.string.format.aspx)メソッドで書式を指定**
* **書式を指定して、StringBuilderに文字列を追加（[StringBuilder.AppendFormat]()）**
* **書式を指定して、文字列挿入（C# 6.0）**


##1.3. IConvertibleインターフェース

###概要

[IConvertible](https://msdn.microsoft.com/ja-jp/library/system.iconvertible.aspx)インターフェースは、インスタンスの値をプリミティブ型[^1]及びDateTime構造体に変換するメソッドを持ちます。

[^1]: プリミティブ型 : Boolean、Byte、SByte、Char、Int16、Int32、Int64、UInt16、UInt32、UInt64、Single、Double、Decimal、String

```csharp:IConvertible
[ComVisibleAttribute(true)]
public interface IConvertible {
	TypeCode GetTypeCode();
	bool ToBoolean( IFormatProvider provider );
	byte ToByte( IFormatProvider provider );
	char ToChar( IFormatProvider provider );
	DateTime ToDateTime( IFormatProvider provider );
	decimal ToDecimal( IFormatProvider provider );
	double ToDouble( IFormatProvider provider );
	short ToInt16( IFormatProvider provider );
	int ToInt32( IFormatProvider provider );
	long ToInt64( IFormatProvider provider );
	sbyte ToSByte( IFormatProvider provider );
	float ToSingle( IFormatProvider provider );
	string ToString( IFormatProvider provider );
	object ToType( Type conversionType, IFormatProvider provider );
	ushort ToUInt16( IFormatProvider provider );
	uint ToUInt32( IFormatProvider provider );
	ulong ToUInt64( IFormatProvider provider );
}
```

実装するメソッドが17個とちょっと多めですね（汗）。

###このインターフェースを継承して実装することでできること

* **[Convert](https://msdn.microsoft.com/ja-jp/library/system.convert.aspx)クラスを利用してプリミティブ型及びDateTime型に変換する**


##1.4. IEquatable\<T\>インターフェース

###概要

[IEquatable\<T\>](https://msdn.microsoft.com/ja-jp/library/ms131187.aspx)インターフェースは、2つのインスタンスの値が等しいかどうかを判別する[Equals](https://msdn.microsoft.com/ja-jp/library/ms131190.aspx)メソッドを持ちます。

```csharp:IEquatable(T)
[ComVisibleAttribute(true)]
public interface IEquatable<in T> {
	bool Equals( T other );
}
```

###このインターフェースを継承して実装することでできること

* **リストにおける指定した要素の存在の確認したり、インデックスの位置を求めたりする（[List\<T\>.Contains](https://msdn.microsoft.com/ja-jp/library/bhkz42b3.aspx) / [List\<T\>.IndexOf](https://msdn.microsoft.com/ja-jp/library/e4w08k17.aspx)）などの処理**
* **HashSet\<T\>やDictionary\<T\>のキーの比較[^2]**

[^2]: まずGetHashCodeメソッドの戻り値を比較し、値が等しい時にIEquatable\<T\>.Equalsメソッドが呼び出されます。

#2. インターフェースの実装例

ここでは固定小数点演算を扱う構造体における、インターフェースの実装例を載せます。

```csharp:固定小数点演算を扱う構造体における、インターフェースの実装例
using System;
using System.Text.RegularExpressions;

public struct Q10 : IComparable, IFormattable, IConvertible, 
	IComparable<Q10>, IEquatable<Q10> {

	// 固定小数点演算の内部の値
	public short Value { get; private set; }

	// IComparableの実装
	int IComparable.CompareTo( object obj ) {
		// objがnull or Q10以外の時
		if( obj == null || !( obj is Q10 ) ){
			return 1;
		}
		// 内部の値同士で比較します。
		return Value.CompareTo( ( ( Q10 )obj ).Value );
	}

	// IComparable<Q10>の実装
	int IComparable<Q10>.CompareTo( Q10 other ) {
		// 内部の値同士で比較します。
		return Value.CompareTo( other.Value );
	}

	// IEquatable<Q10>の実装
	bool IEquatable<Q10>.Equals( Q10 other ) {
		// 内部の値同士で比較します。
		return Value.Equals( other.Value );
	}

	// IConvertibleの実装（一部のみ抜粋）
	double IConvertible.ToDouble( IFormatProvider provider ) {
		// 内部の値 / 1024.0 ⇒ 等価な倍精度浮動小数点数に変換します。
		return Value / 1024.0;
	}

	int IConvertible.ToInt32( IFormatProvider provider ) {
		// 内部の値 / 1024 ⇒ 等価な符号付き32ビット整数に変換します。
		return Value >> 10;
	}

	static Regex regf = new Regex( @"F\d*?|f\d*?" );
	static Regex regd = new Regex( @"ID\d*?|id\d*?" );

	// IFormattableの実装
	string IFormattable.ToString( string format, IFormatProvider provider ) {
		if( format == null ) {
			return ( Value / 1024.0 ).ToString();
		}
		// 書式が「F[数字]」の時は、等価な倍精度浮動小数点数を書式指定して文字列に変換します。
		else if( regf.IsMatch( format ) ) {
			return ( Value / 1024.0 ).ToString( format );
		}
		// 書式が「ID[数字]」の時は、内部の値を書式指定して文字列に変換します。
		else if( regd.IsMatch( format ) ) {
			return Value.ToString( format );
		}

		throw new FormatException();
	}

}
```

インターフェースを上手に利用して、作成するクラスや構造体をより便利にしてみてはいかがでしょう。
それでは、See you next!