7
9

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#】

Last updated at Posted at 2022-01-23

UnityでC#のスクリプトを書いている中で、同じクラスに複数の処理をすることがあり、メソッドチェーンにできないかと工夫した結果を共有します。

メソッドチェーンとは?

これはどのプログラミング言語にも共通する概念ですが、複数の処理を一つの式(Expression)にまとめることを言います。身近なもので言えば、JavaScriptではPromiseの使い方がメソッドチェーンですね。

axios.get("http://example.com")
  .then(res => console.log(res))
  .catch(err => console.log(err));

C#でもメソッドチェーンを書くことができます。例えば

using System;

public class Chain
{
    private string _text;

    public Chain SetText(string text)
    {
        _text = text;
        return this;
    }
    
    public void Speak()
    {
        Console.WriteLine(_text);
    }
}

new Chain()
    .SetText("Hello!")
    .Speak();

こんな感じです。

継承が入ると…?

しかし、継承が混じってくると簡単にはいきません。例えば、

using System;

public abstract class BaseChain
{
    private string _text;

    public BaseChain SetText(string text)
    {
        _text = text;
        return this;
    }

    public virtual void Speak()
    {
        Console.WriteLine(_text);
    }
}

public class ColorfulChain : BaseChain
{
    private ConsoleColor _color;

    public ColorfulChain SetColor(ConsoleColor color)
    {
        _color = color;
        return this;
    }
    
    public override void Speak()
    {
        Console.ForegroundColor = _color;
        base.Speak();
    }
}

BaseChainColorfulChainの親クラスだとします。この時、以下はコンパイルできますが、

new ColorfulChain()
    .SetText("Hello!")
    .SetColor(ConsoleColor.Blue)
    .Speak();

以下はコンパイルが通りません。

new ColorfulChain()
    .SetColor(ConsoleColor.Blue)
    .SetText("Hello!")
    .Speak();

これは困ったものです。今出した例はメソッドがそれぞれ一つしかありませんからまだ耐えることには耐えますが、メソッドが増えてきたり、一回の継承じゃなくて何回も継承していくと流石に堪えます。

Genericsと拡張メソッドの出番

これはC#のGenericと拡張メソッドで解消することができます。型が変数になるっていいことですね本当に。

using System;

public abstract class BaseChain
{
    internal string text;

    public virtual void Speak()
    {
        Console.WriteLine(_text);
    }
}

public static class BaseChainExtension
{
    public static T SetText<T>(this T chain, string text) where T : BaseChain
    {
        chain.text = text;
        return chain;
    }
}

public class ColorfulChain : BaseChain
{
    private ConsoleColor _color;

    public ColorfulChain SetColor(ConsoleColor color)
    {
        _color = color;
        return this;
    }
    
    public override void Speak()
    {
        Console.ForegroundColor = _color;
        base.Speak();
    }
}

new ColorfulChain()
    .SetColor(ConsoleColor.Blue)
    .SetText("Hello!")
    .Speak();

まず、BaseChainExtensionという静的クラスを作ります。これは継承元の親クラスに実装するべきだったメソッドチェーンを実装するクラスとなります。

SetTextの定義を見てみましょう。

public static T SetText<T>(this T chain, string text) where T : BaseChain { }

this T chainとかくことで、自分自身の型がTと解釈され(ここではT = ColorfulChain)、返り値の型にTを書くことができます。今のところ小クラスの型を親クラス側のメソッドの実装で静的に取得(?)するのはこれだけなのかな?と勝手に思っています。もし間違ってたら教えてください。

また、型パラメタTに対してwhere節を書く必要があります。なぜかというと、親クラスのメンバにアクセス(ここではchain.textにアクセス)するためにキャストが必要になり、実行時安全性が幾分か損なわれ得るからです。

さいごに

このように、継承を交えたメソッドチェーンをC#で書く場合にはGenericsと拡張メソッドを使えばよいのです。

メソッドチェーンってかけると気持ちいいし、なにせかっこいいじゃないですか!だから継承クラスにも使えるようにしたかったのです。

だれかのお役に立てることを願っています。

7
9
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
7
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?