標準クラスライブラリ中のインターフェース

.NET Framework の標準クラスライブラリでは、汎用性の高いいくつかのインターフェースを標準で用意しています。 ここでは、そのうちのいくつかを紹介します。

IComparable

IComparable<T>インターフェイス(System名前空間)は、順序比較ができるものを表します。 配列の整列などに使います。

using System;
using System.Linq;

/// <summary>
/// 2次元上の点。
/// <see cref="IComparable{T}"/> を実装している = 順序をつけられる。
/// </summary>
class Point2D : IComparable<Point2D>
{
    public double X { get; }
    public double Y { get; }

    public Point2D(double x, double y)
    {
        X = x;
        Y = y;
    }

    public double Radius => Math.Sqrt(X * X + Y * Y);
    public double Angle => Math.Atan2(Y, X);

    /// <summary>
    /// 距離で順序を決める。
    /// 距離が全く同じなら偏角で順序付け。
    /// </summary>
    /// <param name="other"></param>
    /// <returns></returns>
    public int CompareTo(Point2D other)
    {
        var r = Radius.CompareTo(other.Radius);
        if (r != 0) return r;
        return Angle.CompareTo(other.Angle);
    }
}


class IComparableSample
{
    public static void Main()
    {
        const int N = 5;
        var rand = new Random();
        var data = Enumerable.Range(0, N).Select(_ => new Point2D(rand.NextDouble(), rand.NextDouble())).ToArray();

        Console.WriteLine("元:");
        foreach (var p in data) WriteLine(p);

        // 並べ替えの順序に使える
        Console.WriteLine("整列済み:");
        foreach (var p in data.OrderBy(x => x)) WriteLine(p);
    }

    private static void WriteLine(Point2D p)
    {
        Console.WriteLine($"({p.X:N3}, {p.Y:N3}), radius = {p.Radius:N3}, angle = {p.Angle:N3}");
    }
}

コレクション

コレクション(参考: 「コレクション概要」)には、 同じ操作ができる様々な実装方法があります(それぞれにメリット・デメリット、適切な利用場面があります)。

そして、C#では、操作の種類ごとにインターフェイスが標準で用意されていて、コレクションはそれらのインターフェイスを実装します。 以下の表示いくつか例を挙げます(いずれもSystem.Collections.Generic名前空間)。 (詳しくはMSDNをご覧ください。)

インターフェイス 説明
IEnumerable<T> 要素の列挙ができる。foreachステートメントや、LINQ to Objects で使える。
ICollection<T> IEnumerable<T>に加えて、要素の追加(Add)、削除(Remove)などができたり、要素の個数が取れる。
IList<T> ICollection<T>に加えて、インデクサーを使った要素の読み書きができる。
IDictionary<TKey, TValue> 辞書アクセス(キーを使った値の検索)しての値の読み書きができる。
IReadOnlyCollection<T> IEnumerable<T>に加えて、要素の個数が取れる。読み取り専用なので共変
IReadOnlyList<T> IReadOnlyCollection<T>に加えて、インデクサーを使った要素の読み取りができる。読み取り専用なので共変
IReadOnlyDictionary<TKey, TValue> 辞書アクセス(キーを使った値の検索)しての値の読み取りができる。
Ver. 5.0

読み取り専用系のインターフェイスは .NET Framework 4.5 (C# 5.0と同時期)で追加されました。

このうち、IEnumerableIReadIReadOnlyListの例を挙げておきます。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

/// <summary>
/// 連結リスト。
/// <see cref="IEnumerable{T}"/> を実装している = データの列挙ができる。複数のデータを束ねてる。
/// </summary>
/// <typeparam name="T"></typeparam>
class LinkedList<T> : IEnumerable<T>
{
    public T Value { get; }
    public LinkedList<T> Next { get; }

    public LinkedList(T value) : this(value, null) { }
    private LinkedList(T value, LinkedList<T> next) { Value = value; Next = next; }

    public LinkedList<T> Add(T value) => new LinkedList<T>(value, this);

    public IEnumerator<T> GetEnumerator()
    {
        if(Next != null)
            foreach (var x in Next)
                yield return x;
        yield return Value;
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

class IEnumerableSample
{
    public static void Main()
    {
        var a = new LinkedList<int>(1);
        var b = a.Add(2).Add(3).Add(4);

        // foreach で使える(これは IEnumerable 必須ではない)
        foreach (var x in b)
            Console.WriteLine(x);

        // string.Join で使える
        Console.WriteLine(string.Join(", ", b));

        // LINQ で使える
        Console.WriteLine(b.Sum());
    }
}
using System;
using System.Collections;
using System.Collections.Generic;

/// <summary>
/// 4次元上の点。
/// <see cref="IReadOnlyList{T}"/> を実装している = <see cref="IEnumerable{T}"/>に加えて、インデックス指定で値を読める。
/// </summary>
class Point4D : IReadOnlyList<double>
{
    public double X { get; }
    public double Y { get; }
    public double Z { get; }
    public double W { get; }

    public Point4D(double x, double y, double z, double w) { X = x; Y = y; Z = z; W = w; }

    public double this[int index]
    {
        get
        {
            switch (index)
            {
                default:
                case 0: return X;
                case 1: return Y;
                case 2: return Z;
                case 3: return W;
            }
        }
    }

    public int Count => 4;

    public IEnumerator<double> GetEnumerator()
    {
        yield return X;
        yield return Y;
        yield return Z;
        yield return W;
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

class IReadOnlyListSample
{
    public static void Main()
    {
        var p1 = new Point4D(1, 2, 3, 4);
        var p2 = new Point4D(3, 7, 5, 11);

        // X, Y, Z, W の代わりに 0, 1, 2, 3 のインデックスで値を読み出し
        var innerProduct = 0.0;
        for (int i = 0; i < 4; i++)
            innerProduct += p1[i] * p2[i];

        Console.WriteLine(innerProduct);
    }
}

IDisposable

IDisposableインターフェイス(System名前空間)は、ガベージ コレクション任せではなく、 明示的なタイミングで破棄処理を行いたいものに使います。詳細は「リソースの破棄」で説明します。

using System;

/// <summary>
/// <see cref="IDisposable"/> を実装している = 使い終わったら明示的に Dispose を呼ぶ必要がある。
/// </summary>
class Stopwatch : IDisposable
{
    System.Diagnostics.Stopwatch _s = new System.Diagnostics.Stopwatch();

    public Stopwatch() { _s.Start(); }

    public void Dispose()
    {
        _s.Stop();
        Console.WriteLine(_s.Elapsed);
    }
}

class IDisposableSample
{
    public static void Main()
    {
        // using ブロックを抜けたら自動的に Dispose が呼ばれる
        using (new Stopwatch())
        {
            var t = T(12, 6, 0);
        }
    }

    private static int T(int x, int y, int z) => x <= y ? y : T(T(x - 1, y, z), T(y - 1, z, x), T(z - 1, x, y));
}

更新履歴

ブログ