BEACHSIDE BLOG

Azure と GitHub と C# が好きなエンジニアの個人メモ ( ・ㅂ・)و ̑̑

Xamarin.Forms の MVVM 基礎 ( Petzold's book - chapter.18 その3)

Xamarin.Forms での MVVM の基礎について、通称 Petzold's book の18章「MVVM」をざっくり試しています。
前回の続きです。

Overview

Petzold's bookの内容に沿ってにざっくり進めます。

  • 1. MVVM interrelationships(chapter.18 その1
    • MVVMの概要説明
  • 2. ViewModels and data binding(chapter.18 その1
    • 時計の表示をMVVMで行うサンプル
  • Interactive properties in a ViewModel(chapter.18 その2
    • スライダーの値を掛け算するサンプル
  • A Color ViewModel(chapter.18 その2
    • 色の変化を楽しむ?サンプル
  • Streamlining the ViewModel(今回)
    • 「ViewModelの簡素化」についてまとめました(...本文を要約)
  • The Command interface 1(chapter.18 その4
    • ICommandインターフェースの概要
    • 3の累乗計算アプリのサンプル
  • The Command interface 2(chapter.18 その5
    • 簡易足し算アプリのサンプル
  • ViewModels and the application lifecycle(chapter.18 FINAL

Environment

検証した環境は以下で前回同様2016-09-29時点で最新の状態にしています。

2. ViewModels and data binding(の続き)

513ページ、「Streamlining the ViewModel」...ViewModelの簡素化についてです。今回は、本文を私なりに要約してみました。それでは進めましょう。

 

Streamlining the ViewModel

INotifyPropertyChangedインターフェースの典型的な実装では、例えば、クラスで定義されたすべてのパブリックプロパティに、以下のようなバッキングフィールドを持っています。

double number; 

また、PropertyChangedイベントを発火する責務を持ったOnPropertyChangedメソッドも持っています。

public event PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged(string propertyName)
{
    PropertyChangedEventHandler handler = PropertyChanged;

    if (handler != null) 
    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public double Number
{
    set
    {
        if (number != value)
        {
            number = value; OnPropertyChanged("Number");

            // Do something with the new value. 

        }
    }
    get { return number; }
}

OnpropertyChangeメソッドの引数に文字列を渡すということは潜在的な問題です。
バッキングフィールドは、1つのプロパティの中で3回も出現しますが、もしスペルを間違っても、エラーメッセージの出力はないしプロパティは正しく動作しません。
もし、類似のプロパティをいくつも持っていてコピペで書いた場合、1プロパティ毎に3つもあるバッキングフィールドの一つをコピペし忘れることはあるかもしれないし、そのバグをトレースするのは大変でしょう。

この問題は、C#5.0の機能で解決できます。
CallerMemberNameAttributeクラスを使うと、呼び出し元のメソッド名やプロパティ名を、オプション引数の値と置換してくれます。この機能を使ってOnPropertyChangedメソッドを再定義してみましょう。引数にCallerMemberName属性をつけて、初期値NULLのオプション引数にします。名前空間には、 System.Runtime.CompilerServicesが必要です。

using System.Runtime.CompilerServices;

...

protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

Numberプロパティは、OnPropertyChangedメソッドを、プロパティ名の引数無しで呼ぶことができるようになりました。OnPropertyChangedメソッドの引数には、呼び出し元のプロパティ名である"Number”が自動的にセットされます。

public double Number
{
    set
    {
        if (number != value)
        {
            number = value;
            OnPropertyChanged();

            // Do something with the new value.         
        }
    }
    get  {  return number;  } 
}

このアプローチによって、プロパティ名のスペルミスは回避できますし、プログラミング中のプロパティ名の変更の心配もなくなります。実際のところ、CallerMemberName属性が実装された重要な理由のひとつは、INotifyPropertyChangedインターフェースを実装するクラスを単純化するためでした。

しかしながら、これが機能するのは、値が変更されたプロパティがOnPropertyChagedメソッドを呼び出した場合のみです。(前回のサンプルで登場した)ColorViewModelの場合、Colorプロパティの値変更の場合を除き、明示的にプロパティ名の入力が必要です。

セッターの単純化について掘り下げてみます。以下のようなジェネリクスのメソッド(メソッド名は"SetProperty"、 またそんな感じの名称)を作る必要があります。以下のSetPropertyメソッドは、CallMemberName属性を使って定義されています。

bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
    if (Object.Equals(storage, value)) return false;

    storage = value;  
    OnPropertyChanged(propertyName);
    return true;
}

protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

SetPropertyメソッドの最初の引数は、バッキングフィールドへの参照です。2つめの引数は、プロパティにセットする値です。このSetPropertyメソッドは、値のチェックとセットを自動化してくれます。

注意として、OnPropertyChangedメソッドを呼ぶときに、propertyName を明示的に含めています。(そうしないと、propertyName引数の値はいつでも”SetProperty”になってしまいます。)
このSetPropertyメソッドは、プロパティの値が変更されたTrueを返します。それによって追加の処理を加えることもできます。

public double Number
{
    set
    {
        if (SetProperty(ref number, value))
        {
            // Do something with the new value.        
        }
    }
    get { return number; }
}

SetProperty はジェネリクスのメソッドなので、引数からC#コンパイラーは型を推論することができます。もし、プロパティのセッターでの新しい値を使って何か処理する必要がなければ、Stterを一行で書くこともできます。

public double Number
{
    set{ SetProperty(ref number, value)); }
    get { return number; }
}

この合理性を気に入ったら、独自のクラスでSetPropertyとOnPropertyChangedメソッドを作り、VieModelクラスを作るときに派生させたいと思うでしょう。 ViewModelBaseと呼ばれるこのようなクラスは、以下のXamarin.FormsBook.Toolkitライブラリに既にあります。

xamarin-forms-book-samples/ViewModelBase.cs at master · xamarin/xamarin-forms-book-samples · GitHub

これ以降の18章の残り2つのサンプルでは、このViewModelBaseクラスを使うということで、ようやく18章の半分を超えたくらいです(:.; ゚Д ゚;.:)=3ハァハァ。

次回に続きます。