1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

はじめに

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

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

例示するアプリの状況

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

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

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

abstract classの使用

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

親class

親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

直列処理の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を指定する。

並列処理の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について、並列処理での実装を記述する。

外部からのアクセス

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

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?