SE(たぶん)の雑感記

一応SEやっている筆者の思ったことを書き連ねます。会計学もやってたので、両方を生かした記事を書きたいと考えています。 でもテーマが定まってない感がすごい。

C#(.NET)の主要なインターフェイス(interface)について

UWPのネタも書きたいところですが、今回はもっと前段階の話で、interfaceについて書きます。
公開されているライブラリを見ると、今や、当たり前のように使われていますし、私が書く記事でも、当たり前のように使っています。

復習を兼ねて、まとめを書いてみたいと思います。

なお、書いたことは.NET全体の話ですが、全部C#と記すので、そこはご了承ください。

リンク集

いつもの、いきなりな感じ(笑)
とりあえず、Microsoftへのリンクです。

インターフェイス (C# リファレンス) | Microsoft Docs

インターフェイス (C# プログラミング ガイド) | Microsoft Docs

あと、私の以前の記事です。

hiroronn.hatenablog.jp

C#のinterface躍進が始まったとき

デザインパターンの話は置いておきます。*1

C#のinterfaceが進化を遂げたのは、C#3.0で追加された、以下の機能*2のおかげと思っています。

  • 拡張メソッド
    ソースの可読性が向上!
  • ラムダ式
    簡単に処理を定義できる!
  • 型推論
  • 匿名クラス

リンク:C# 3.0 の概要

これらを利用して生み出されたのが、Linqです。
これのおかげで大躍進したinterfaceが、IEnumerable<T>です。

IEnumerableと、Linqについて

IEnumerable<T>とは、列挙操作を定義したinterfaceで、有名なところだとList<T>等が実装しています。

列挙操作とは、foreach文等のループを考えると分かりやすいです。

.NET Framework2.0では、List<T>クラスが追加され、従来型を限定した列挙を扱う際に配列ぐらいしかなかった環境において、とても便利なクラスとして利用されていました。*3

ただ、「列挙から条件を満たすものを抽出する」場合、もう一つ列挙を作るしかなく、可読性を下げ、余計なメモリを消費*4する原因となっていました。

Linqだと、「列挙から条件を満たすものを抽出する」という処理が、直感的に記述できます。

例:列挙から条件を満たすものを抽出する

まずは、以下のソースを見て、何をやっているのか考えてみてください。

var result = students
    .Select(s => new {Name = s.Name, Sum = s.Math + s.Japanese + s.English})
    .Where(s2 => s2.Sum >= 210);

foreach(var student in result){
    //上記式の戻り値は、「Name」と「Sum」を持った匿名クラスであるため、回すだけで結果が取れる
}

次はこちら。やることは同じです。

List<Student> result = new List<Student>();

foreach(var student in students) {

    if((student.Math + student.Japanese + student.English) >= 210) {
        result.Add(student);
    }
}

foreach(var student in result){
    //列挙中に合計が必要な場合、また計算する
}

Linq使用のほうは、結果を返すために、新たなリストを生成しません。*5
しかも、「studentから名前と合計を抽出し、合計が210点以上の生徒を返す」という目的が、分かりやすくなっています。

これが、Linqの利点と言われています。

さらに恐ろしいのは、このLinqは、

IEnumerableを実装しているクラスになら、全てに使用できる*6

という利点があります。

List<T>だろうが、配列だろうが、XElementだろうが、一緒の構文で書けます。
学習コストが下がるのは、言うまでもないでしょう。

INotifyPropertyChangedについて

INotifyPropertyChangedは、その名の通り、「プロパティの変更通知を行う」ためのインターフェイスです。
実は、上で紹介した「拡張メソッド」等とはあまりかかわりがないです。

WPFUWPの、高度なデータバインドを支えるインターフェイスです。

定義は、以下の通りです。

public interface INotifyPropertyChanged {
   event PropertyChangedEventHandler PropertyChanged;
}

よくある実装は、以下の通りです。

//イベント実行
protected void OnPropertyChanged(string name) {
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null) {
        handler(this, new PropertyChangedEventArgs(name));
    }
}

//プロパティの変更通知例
public string PersonName {
    get { return name; }
    set {
        if(name == value) {
            return;
        }
        name = value;
        // 値変更を通知する
        OnPropertyChanged("PersonName");
    }
}

外部からは、PropertyChangedイベントを購読することで、「値が変わったプロパティ名」が通知されてくるので、それを使って処理する、といったところです。
が、そうやって使うことはほとんどないです。

これは、WPFUWPMVVMパターンを使う際に利用し、画面側ライブラリで購読するようになっています。
上記の「変更通知」を行うと、Viewがそれを拾い、その名称がバインドしているコントロールのプロパティを、再度読み込んでくれます。

逆に、ユーザー入力等で値が変わった場合は、それをプロパティに通知してくれます。

余談

INotifyPropertyChangedですが、イベントの引数で「プロパティ名」を要求しています。
文字列なので、「参照の検索」で見つからない、タイプミス等、様々な問題が発生していました。

それを解決するために、様々な手段が存在しています。ライブラリで解決しているものが多いです。
なお、.NET Frameworkレベルで解決しているものはあまりないため、利用する際は「チームで方針を決める」べきです。

StackOverflowにまとまっています。

stackoverflow.com

  • OnPropertyChangedの引数をExpression<Func<T>>にする
  • 自作の属性を用意し、コンパイル時に自動的に変更通知の形にする
  • プロパティ名受け取りにCallerMemberName属性を利用する

Prismなら、BindableBaseViewModelBaseを継承し、以下のいずれかを使いましょう。
使い分けは、場合によりけりですが、新しく作るならSetPropertyが良いと思います。(以下、定義)

protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null);
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression);
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null);

//使用例
public string Title {
    get { return _title; }
    private set { SetProperty(ref _title, value); }
}

IObservable、IObserverについて

.NET Framework4.0でひっそりと追加されたインターフェイスです。
Taskの陰に隠れちゃった

これは、デザインパターンの一つ、Observerパターンの.NET実装です。 両インターフェイスは、定義としては単純で、

  • IObservable
    イベント購読用メソッドを公開し、何か起こったら、イベントを発行する

  • IObserver
    IObservable<T>が発行したイベントから通知を受け取る

だけです。

IObservable.Subscribeでイベントを購読しますが、戻り値がIDisposableであるため、若干癖があります。
とはいえ、実装例は豊富なので、問題ないと思います。

これだけだと、eventキーワードで実装できるイベントと、大して変わりません。

Reactive Extensions の登場

Reactive Extensionsは、Microsoftが作成している、ライブラリのことです。

NuGetで入手できますが、ライブラリ名はSystem.Reactive等になっています。

qiita.com

私は、いまだに使う機会に恵まれません。
これらも、拡張メソッドが利用されています。あと、ラムダ式も多用されます。*7

.NETだと、Linqを黒魔術して生まれた何か、みたいな言い方をされます(笑)
なお、.NETでの定義が非常に簡潔だったため、多言語にも簡単に移植できることから、様々な言語で実装されています。

ReactiveX - Languages

うまく説明はできないのですが…

Linqは、連続した値(シーケンス)を扱うライブラリですが、Rxは、そのシーケンスに「時間軸」の概念を取り入れています。
「時間軸で連続した値」であることから、Linqでも扱える、的な。

…んー、まぁ、
UWP使う時に勉強します。。。(すみません)

最後に

途中で書いた、IDisposableですが、これも主要なインターフェイスです。
ただ、ちょっとまとめるには難しい部分が多いので、割愛しました。

個人的に思う、.NETの特徴と思える部分を選んで書きました。
C#を触れるきっかけにでもなれば。


*1:デザインパターンを含めるなら、C#の初期からinterfaceは有用

*2:このおかげで、ジェネリクスやyield returnの価値も上昇した

*3:型指定なしの、非ジェネリクスのList等は、.NET1.0のころから存在した

*4:Listを作成するコスト、列挙全体を走査するコスト、項目への参照を保持するコスト等

*5:yield return により、ループ時点で評価された結果を、順次返してくる

*6:IEnumerableの拡張メソッドとして、定義されているため

*7:無いと書けないレベル