「C Sharpの基礎 - インターフェイス」の版間の差分
331行目: | 331行目: | ||
<br> | <br> | ||
例えば、標準ライブラリのIEquatable<T>インターフェイス(System名前空間)について、異なる型・引数で複数実装できる。<br> | 例えば、標準ライブラリのIEquatable<T>インターフェイス(System名前空間)について、異なる型・引数で複数実装できる。<br> | ||
AとBの2つのクラスがある時、IEquatable<A>とIEquatable<B>という2つの実装を持つことができる。<br> | AとBの2つのクラスがある時、IEquatable<A>とIEquatable<<nowiki>B</nowiki>>という2つの実装を持つことができる。<br> | ||
<br> | <br> | ||
具体的な用途として、以下のような場面で有効である。<br> | 具体的な用途として、以下のような場面で有効である。<br> | ||
337行目: | 337行目: | ||
* Shapeクラスから派生した矩形型Rectangleクラスがある。<br>Rectangleクラスは、縦横の両方の比較で等値判定する。 | * Shapeクラスから派生した矩形型Rectangleクラスがある。<br>Rectangleクラスは、縦横の両方の比較で等値判定する。 | ||
* Shapeクラスから派生した円型Circleクラスがある。<br>Circleクラスは、半径の比較で等値判定する。 | * Shapeクラスから派生した円型Circleクラスがある。<br>Circleクラスは、半径の比較で等値判定する。 | ||
* | * Shapeクラスは、矩形同士、円同士でのみ等値判定する。<br>型が異なる場合は、その時点で不一致となる。 | ||
<br> | <br> | ||
この条件下において、各クラスに以下のようなインターフェイスを持つことができる。<br> | この条件下において、各クラスに以下のようなインターフェイスを持つことができる。<br> |
2021年3月16日 (火) 03:36時点における版
概要
インターフェースとは、クラスが実装すべき規約を定めるものである。
インターフェイスには、非抽象クラスまたは構造体で実装する必要がある関連する機能のグループに対する定義が含まれる。
特に、C#はクラスの多重継承ができないため、インターフェイスは重要である。
また、C# 8.0以降では、メンバの既定の実装を定義できる。
ただし、インターフェイスでは、フィールド、自動実装プロパティ、プロパティに似たイベント等は宣言できない。
構造体を継承する場合は、インターフェイスを使用する必要がある。
C#の構造体は、別の構造体またはクラスから継承することができないからである。
以下に、インターフェイスの特徴を示す。
- publicの抽象メソッドのみを持つクラスのようなもの。
- インターフェイスを定義する時は、interfaceキーワードを使用する。
- 抽象クラスとは異なり、複数のインターフェースを継承できる。
- C# 8.0以降では、メンバの既定の実装を定義できる。
インターフェース
インターフェースとは、規約のみを定めるものである。
C#では、抽象メソッドを使用することでメソッドの規約のみを定めることができる。
つまり、C#のインターフェースとは、抽象メソッドのみを持つ抽象クラスだと考えることができる。
C#のインターフェースの定義は以下のように記述する。
interface インターフェース名
{
// メソッド・プロパティの宣言
}
インターフェースの実装は、クラスの継承と同じ構文である。
class クラス名 : インターフェース名
{
// クラスの定義
}
インターフェースには、以下のような特徴がある。
- メンバ変数(フィールド)を持つことができない。
- staticメソッドを持つことができない。
- 宣言したメソッドおよびプロパティは、全て
public abstract
になる。 - 1つのクラスが複数のインターフェースを実装(多重継承)できる。
C# 8.0では、制限がいくつか緩和されている。
機能面でいうと、クラスと抽象クラスとの差は、メンバ変数(フィールド)を持てない代わりに多重継承できる程度である。
標準クラスライブラリのインターフェース
.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
名前空間)
インターフェイス | 説明 |
---|---|
IEnumerable<T> | 要素の列挙ができる。 foreach文やLINQ to Objectsで使用する。 |
ICollection<T> | IEnumerable<T>に加えて、要素の追加(Add)、削除(Remove)、要素の個数の取得ができる。 |
IList<T> | ICollection<T>に加えて、インデクサを使用した要素の読み書きができる。 |
IDictionary<TKey, TValue> | 辞書アクセス(キーを使った値の検索)しての値の読み書きができる。 |
IReadOnlyCollection<T> .NET Framework 4.5以降とC# 5.0以降 |
IEnumerable<T>に加えて、要素の個数を取得できる。 読み取り専用なので共変。 |
IReadOnlyList<T> .NET Framework 4.5以降とC# 5.0以降 |
IReadOnlyCollection<T>に加えて、インデクサを使用した要素の読み取りができる。 読み取り専用なので共変。 |
IReadOnlyDictionary<TKey, TValue> .NET Framework 4.5以降とC# 5.0以降 |
辞書アクセス(キーを使った値の検索)しての値の読み取りができる。 |
上表のうち、IEnumerable<T>
とIReadIReadOnlyList<T>
の例を示す。
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));
}
複数のインターフェイスの実装(多重継承)
C#は、クラスの多重継承ができないが、インターフェイスの複数の実装ができる。
struct Id : IComparable<Id>, IEquatable<Id>
{
public int Value { get; set; }
public int CompareTo(Id other) => Value.CompareTo(other.Value);
public bool Equals(Id other) => Value == other.Value;
}
型・引数が異なるジェネリックインターフェイス
C#では、オーバーロードが可能な限り、同名のメンバを持つ複数のインターフェイスを実装することができる。
(オーバーロードできない場合は、次のセクション"明示的実装"が必要になる)
これは、ジェネリックインターフェイスにおいて、型・引数が異なる場合、複数実装する時に効果がある。
例えば、標準ライブラリのIEquatable<T>インターフェイス(System名前空間)について、異なる型・引数で複数実装できる。
AとBの2つのクラスがある時、IEquatable<A>とIEquatable<B>という2つの実装を持つことができる。
具体的な用途として、以下のような場面で有効である。
- 図形全般を表すShapeクラスがある。
- Shapeクラスから派生した矩形型Rectangleクラスがある。
Rectangleクラスは、縦横の両方の比較で等値判定する。 - Shapeクラスから派生した円型Circleクラスがある。
Circleクラスは、半径の比較で等値判定する。 - Shapeクラスは、矩形同士、円同士でのみ等値判定する。
型が異なる場合は、その時点で不一致となる。
この条件下において、各クラスに以下のようなインターフェイスを持つことができる。
- Shapeクラスは他のShapeクラスと比較できるので、IEquatable<Shape>を実装できる。
- Rectangleクラスは他のRectangleクラスと比較できるので、IEquatable<Rectangle>を実装できる。
RectangleクラスはShapeクラスから派生しているため、IEquatable<Shape>でもある。 - Circleクラスは他のCircleクラスと比較できるので、IEquatable<Circle>を実装できる。
CircleクラスはShapeクラスから派生しているので、IEquatable<Shape>でもある。
以下の例では、上記のような用途を実装している。
using System;
abstract class Shape : IEquatable<Shape>
{
public abstract bool Equals(Shape other);
}
class Rectangle : Shape, IEquatable<Rectangle>
{
public double Width { get; set; }
public double Height { get; set; }
public override bool Equals(Shape other) => Equals(other as Rectangle);
public bool Equals(Rectangle other) => other != null && Width == other.Width && Height == other.Height;
}
class Circle : Shape, IEquatable<Circle>
{
public double Radius { get; set; }
public override bool Equals(Shape other) => Equals(other as Circle);
public bool Equals(Circle other) => other != null && Radius == other.Radius;
}