C# abstract classを使ってみようと考える局面

# はじめに

C#を教科書で学ぶと豊富な機能があることがわかるが、UWPやWPFで単機能のシンプルなアプリを開発しているうちは、あまり出番のないものもある。

ところが、アプリの機能が増え、複雑になってくると、出番が出てくる。一例として、`abstract class`を使ってみようと考える局面を書き出してみる。

# 例示するアプリの状況

画像を編集するアプリで、ピクセル(画素)毎の明度と彩度の統計情報の収集と増減を司るclassを考える。当初、CPUで1ピクセルずつ順次処理する実装(直列処理)を行う。動作を確認し、一連のアルゴリズムが検証できたところで、CPUのベクトル演算を用いた高速化の実装(並列処理)を追加する。二つの実装はオプションの設定で切り替え可能にする。

- 対処1: 条件文で直列処理と並列処理を切り分ける。コードが長くなる、インデントが深くなるなど見通しが悪くなる、などの弊害が出てくる。
- 対処2: 直列処理と並列処理で別のclassにする。共通のPropertyやMethodのコードが重複する。

これらの弊害を抑えることを考えると、`abstract class`の使用が思いつく。

# abstract classの使用

元のclassを、abstract指定する親classと、直列処理と並列処理で異なる差分の実装を行う子classに分割する。

## 親class

```C#:親class
public abstract class LightnessSaturation : BitmapByteArrayManager, INotifyPropertyChanged
{
    // 直列処理と並列処理で共通のPropertyの実装
    private SoftwareBitmap _EditingImage;
    public SoftwareBitmap EditingImage
    {
        get { return _EditingImage; }
        set { _EditingImage = value; }
    }
    ...

    // 直列処理と並列処理で共通のMethodの実装
    public void ClearStats()
    {
        _LAverage = 0.0f;
        _SAverage = 0.0f;
        ...
    }
    ...

    // 直列処理と並列処理で実装を分けるMethod
    public abstract Task GatherStats();
    public abstract Task IncrementLightness();
    public abstract Task DecrementLightness();
    ...
}
```

元のclassに`abstract`を付し、直列処理と並列処理で共通のPropertyやMethodはそのままに、実装を分けるMethodは`abstract`指定で定義する。

## 子class

```C#:直列処理のclass(子の1)
public class LSOpCpu : LightnessSaturation
{
    // 親classでabstractで定義したMethodの実装
    public override async Task GatherStats()
    {
        int npxls = 0;
        if (!SrcImgInitialized)
        {
            // 親のabstract classで定義したPropertyを使用
            SourceBitmap = EditingImage;
            ...
        }
        // CPUによる直列処理の実装
        npxls = SourceNPxls;
        for (int i = 0; i < npxls; i++)
        {
            ...
        }
        ...
    }

    public override async Task IncrementLightness()
    {
        ...
    }

    public override async Task DecrementLightness()
    {
        ...
    }
}
```

子のclassは、親のabstract classを継承する。元の親classにあった直列処理のMethodの実装は、子のclassに移す。親classではMethodをabstractで定義したので、子classではoverrideを指定する。

```C#:並列処理のclass(子の2)
public class LSOpSimd : LightnessSaturation
{
    // 親classでabstractで定義したMethodの実装
    public override async Task GatherStats()
    {
        int npxls = 0;
        if (!SrcImgInitialized)
        {
            // 親のabstract classで定義したPropertyを使用
            SourceBitmap = EditingImage;
            ...
        }

        // System.NumericsのVectorを用いた並列処理の実装
        Vector<float> vmin = new Vector<float>();
        Vector<float> vmax = new Vector<float>();
        ...
    }

    public override async Task IncrementLightness()
    {
        ...
    }

    public override async Task DecrementLightness()
    {
        ...
    }
    ...
}
```

並列処理の方も同じ。親classでabstractで定義したMethodについて、並列処理での実装を記述する。

## 外部からのアクセス

```C#:MainPage.xaml.csの処理
public sealed partial class MainPage : Page
{
    // 変数定義
    private LightnessSaturation _LightnessSaturation;
    private LSOpCpu _LSOpCpu;
    private LSOpSimd _LSOpSimd;
    ...

    // 初期化処理
    protected override async void OnNavigatedTo(NavigationEventArgs e)
    {
        ...
        _LSOpCpu = new LSOpCpu();
        _LSOpSimd = new LSOpSimd();
        _LightnessSaturation = _LSOpCpu;  // default
        ...
    }

    // アプリで画像の明度照度編集ボタンが押されたときの処理
    private void OpenLSCommand()
    {
        // アプリの設定情報から直列処理か並列処理を選択
        NumericCalc opt = _Settings.NumericCalcParams.Items[_Settings.SelectedNumericCalcParamIdx].CalcMethod;
        switch (opt)
        {
            case NumericCalc.CPU:
                _LightnessSaturation = _LSOpCpu;
                break;
            case NumericCalc.SIMD:
                _LightnessSaturation = _LSOpSimd;
                break;
        }
    }

    // 外部からのアクセス
    private async void GatherLSStats()
    {
        // 親classの実装にアクセス
        _LightnessSaturation.ClearStats();

        // 子classの実装にアクセス
        await _LightnessSaturation.GatherStats();
        ...
    }
    ...
}

```

設定情報を元に、親class変数に子classを代入して直列処理か並列処理を選択する。親class変数はインスタンス化(new)しない。親クラス変数のMethodを呼び出すと、代入状況に応じて子classの実装が呼び出される。

# おわりに

`abstract class`を使うことで、コードが複雑になりすぎるのを防ぎ、個々の課題に集中してコードを書くことができるようになった。今後、さらに別の実装(本例でいえばGPUによる計算など)を追加する際の見通しもよくなった。

C#のあらゆる機能を使いこなせる状態でいることは難しいが、新機能追加の案内のあったときなど、折にふれおさらいしておくと、ちょっと詰まったときに脱出のヒントを得る手がかりになる。