【C#】再帰構造を別の再帰構造に変換する

あるオブジェクトが自分自身をメンバーに持つ、子要素を無限に繰り返して持てる構造を再帰構造とか再帰データと言います。

// 自分自身をメンバーにもつ構造
ClassA
  + ClassA
      + ClassA
        + null // 終端はnull

こういう再帰構造をもつオブジェクトを別の再帰構造を持つオブジェクトに変換する方法を紹介します。

イメージ的には以下の通りです。階層構造を崩さずに別の構造に移し替えます。

// こういう構造を持つクラスを...
ClassA
  + ClassA
      + ClassA
          + ClassA
              + null

// 別の再帰構造をもつクラスに階層構造を維持したまま変換する
// ↓
OtherClass
  + OtherClass
      + OtherClass
          + OtherClass
              + null

確認環境

  • .NET Core 3.1
  • VisualStudio 2022
  • Windows 11

VS上からデバッグ実行で動作を確認しています。

実装例

早速実装していきたいと思います。

使用するデータクラス

先ずは使用するクラスを定義します。自分自身をメンバーに持つクラスを2つ定義します。

public class SampleA
{
    public string Name { get; set; }
    public SampleA Child { get; set; }
}
public class SampleB
{
    public string Key { get; set; }
    public SampleB Child { get; set; }
}

ConvertExtensionsクラス

上記で定義した2つのクラスを相互変換するための拡張メソッドを持つ ConvertExtensions を作成します。

SampleA と SampleB クラスはお互いに関係性がありません。なのでクラス内で余計な依存関係を発生させないように外部に拡張メソッドとして処理を定義します。

namespace MyExtensions
{
    // 相互変換用の拡張クラス
    public static class ConvertExtensions
    {
        // A → B に変換する
        public static SampleB ToB(this SampleA self)
        {
            var top = new SampleB()
            {
                Key = self.Name,
            };

            // 子要素を辿りながら変換
            SampleB current = top;
            SampleA child = self.Child;
            while (child != null)
            {
                current = current.Child = new SampleB()
                {
                    Key = child.Name,
                };
                child = child.Child;
            }
            return top;
        }

        // B → A に変換する
        public static SampleA ToA(this SampleB self)
        {
            var top = new SampleA()
            {
                Name = self.Key,
            };

            SampleA current = top;
            SampleB child = self.Child;
            while (child != null)
            {
                current = current.Child = new SampleA()
                {
                    Name = child.Key,
                }; ;
                child = child.Child;
            }
            return top;
        }
    }
}

使い方

上記実装の使用例は以下の通りです。

using MyExtensions;

private static void Example()
{
    SampleA a = CreateNestedData(3);
    
    // A → B に変換
    SampleB b = a.ToB();

    // B → A に変換、元に戻る
    a = b.ToA();


}

// テストデータ作成
private static SampleA CreateNestedData(int nest)
{
    var top = new SampleA()
    {
        Name = "0",
    };
    SampleA tmp = top;
    for (int i = 0; i < nest; i++)
    {
        var tmpObj = new SampleA()
        {
            Name = (i + 1).ToString(),
        };
        tmp = tmp.Child = tmpObj;
    }
    return top;
}

再帰構造だから再帰呼び出しで処理すると階層構造が数万とかだと StackOverflowException が発生するため、なるべく平らく処理したいなと思って while を使っています。