5
3

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 3 years have passed since last update.

[C#] usingブロックを使ったStringBuilderのインデント処理

Last updated at Posted at 2018-12-03

概要

ツリー構造や集約ルートモデルのデバッグプリントするとき、階層ごとにインデントを入れたいですが、コード上もわかりやすく、簡単にかけるようにならないかと考えて、ユーティリティクラスを作ってみました。

IDisposableにして、 usingブロックを使って書けたらブロックのインデントが出力のインデントに一致してわかりやすい(当社比) かなという思い付きです。

コード(ユーティリティクラス)

コメント欄に改良版がありますので、そちらをご覧ください。取り込もうと思ったけど単なるコピペになりそうなので、自分の元ソースはそのままにしておきます。

using System;
using System.Text;
using System.Text.RegularExpressions;

public class IndentedStringBuilder : IDisposable
{
    private const string INDENT = "  ";
    private const string REPLACEMENT = "$1" + INDENT;
    private StringBuilder _parent;
    private StringBuilder _internal;

    public IndentedStringBuilder(StringBuilder parent)
    {
        _parent = parent;
        _internal = new StringBuilder();
    }

    public StringBuilder Builder { get { return _internal; } }

    /// <summary>
    /// 親のStringBuilderが改行で終わってなかったら改行を追加する
    /// </summary>
    private void AdjustParentEndLine()
    {
        if (_parent.Length == 0 ) return;

        var lastChar = _parent[_parent.Length - 1];
        if(lastChar == '\r' || lastChar == '\n') return;

        _parent.AppendLine();
    }

    public void Dispose()
    {
        AdjustParentEndLine();

        var lines = Regex.Split(_internal.ToString(), "\r\n|\r|\n");
        var count = lines.Length;
        // 元の文字列の末尾が改行だと、空の行が一個増えるのを防ぐ
        if (count > 0 && lines[count - 1].Length == 0) count--;
        lines.Take(count).Foreach(x =>
        {
            _parent.Append(INDENT);
            _parent.AppendLine(x);
        });
    }
}

使い方

こんな風にusingブロックで囲んで使います。ネストして多段インデントもできます。
インデントの前後で必要に応じて改行を追加する機能もありますので、下記の例の場合 AppendLine から Append に変えても同じ出力結果になります。
(当然ですが、同じインデント階層に連続で書き出す場合は Append で改行はしません。)

サンプルコード

    var sb = new StringBuilder();
    sb.AppendLine("AAA");
    using (var child = new IndentedStringBuilder(sb))
    {
        child.Builder.AppendLine("BBB");
        using (var child2 = new IndentedStringBuilder(child.Builder))
        {
            child2.Builder.AppendLine("CCC");
        }
        child.Builder.AppendLine("DDD");
    }
    sb.AppendLine("EEE");
    return sb.ToString();

出力

AAA
  BBB
    CCC
  DDD
EEE

#解説
IDisposableをusingブロックで使うと、ブロックを抜けるときにdisposeされるという仕様を使って、dispose呼ばれたときにインデント付与処理をしています。

IndentedStringBuilder 自体が StringBuilder を継承できればよかったんですが、sealed クラスなのでできません。なのでやむなく Builder プロパティー経由で StringBuilder にアクセスするようにしました。
お好みで IndentedStringBuilder に Append() など StringBuilder と同じメソッド生やしてもいいかもしれません。

言うほど使いやすくも見やすくもねーよ、と言われたらごめんなさいと言うしかありません。そこは主観の問題なのでご容赦ください。

#注意点
usingブロックの中で普通に元のStringBuilderを編集できてしまうけれど、(おそらく)書いた人の期待とは違う結果になります。

    var sb = new StringBuilder();
    sb.AppendLine("AAA");
    using (var child = new IndentedStringBuilder(sb))
    {
        child.Builder.AppendLine("BBB");

        sb.AppendLine("XXX"); // <--これ

        child.Builder.AppendLine("DDD");
    }
    sb.AppendLine("EEE");
    return sb.ToString();

出力

AAA
XXX
  BBB
  DDD
EEE

まあ、XXXBBBDDDの間に挿入したいなら、普通にusingブロックを分割してください。

5
3
4

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
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?