【C#】カッコ(とか)で始まってカッコ(とか)で終わる文字列を取得

[c# カッコで始まってカッコで終わる文字列を取得](https://qiita.com/Implem/items/2dab4c4010309ba5d814)を参考に、自分の都合のいいように作り直した。

囲み文字を()だけじゃなくて、開始と終了でそれぞれ選べるようにした。
あと囲み文字自体も欲しい時はincludeBracket=trueにする。

実行前
(aaa(bbb(ccc)ddd)eee)

includeBracket=falseで段階的に3回実行した時
aaa(bbb(ccc)ddd)eee
bbb(ccc)ddd
ccc


```cs
        /// <summary>
        /// <para>囲み文字内の文字列を取得します。</para>
        /// <para>囲み文字の開始、終了がひもづかない場合は取得できません。</para>
        /// </summary>
        /// <param name="value">文字列。</param>
        /// <param name="startBracket">囲み文字の開始文字。</param>
        /// <param name="endBracket">囲み文字の終了文字。</param>
        /// <param name="includeBracket">結果に囲み文字を含めるかどうか。</param>
        /// <returns>囲み文字内の文字列。取得できない場合は空。</returns>
        public string GetEnclosedText(string value, string startBracket, string endBracket, bool includeBracket = false)
        {
            var started = false;
            var openCount = 0;
            var startIndex = -1;

            // 指定ブラケットに一致するかどうか
            var sb = new StringBuilder();
            Func<int, string, bool> IsMatchBracket = delegate (int index, string targetBracket)
            {
                sb.Clear();
                for (var i = 0; i < targetBracket.Length; i++)
                {
                    sb.Append(value[index + i]);
                }
                var bracket = sb.ToString();

                if (bracket == targetBracket)
                {
                    return true;
                }
                return false;
            };

            // 開始ブラケット処理
            Action<int> startBracketAction = delegate (int index)
            {
                // 文字長を超えるならブラケット処理なし
                if (value.Length - 1 < index + startBracket.Length - 1)
                {
                    return;
                }

                // ブラケットに合致しなければ処理なし
                if (!IsMatchBracket(index, startBracket))
                {
                    return;
                }

                openCount++;
                if (!started)
                {
                    startIndex = index;
                }
                started = true;
            };

            // 終了ブラケット処理
            Action<int> endBracketAction = delegate (int index)
            {
                // 文字長を超えるならブラケット処理なし
                if (value.Length - 1 < index + endBracket.Length - 1)
                {
                    return;
                }

                // ブラケットに合致しなければ処理なし
                if (!IsMatchBracket(index, endBracket))
                {
                    return;
                }
                openCount--;
            };

            for (var index = 0; index < value.Length; index++)
            {
                startBracketAction(index);
                endBracketAction(index);

                if (!started || openCount > 0)
                {
                    continue;
                }

                index += endBracket.Length - 1;
                if (!includeBracket)
                {
                    startIndex += startBracket.Length;
                    index -= endBracket.Length;
                }
                return value.Substring(startIndex, index + 1 - startIndex);
            }
            return value;
        }

        /// <summary>
        /// <para>囲み文字内の文字列を取得します。</para>
        /// <para>囲み文字の開始、終了がひもづかない場合は取得できません。</para>
        /// </summary>
        /// <param name="value">文字列。</param>
        /// <param name="includeBracket">結果に囲み文字を含めるかどうか。</param>
        /// <param name="startBracket">囲み文字の開始文字。</param>
        /// <param name="endBracket">囲み文字の終了文字。</param>
        /// <returns>囲み文字内の文字列。取得できない場合は空。</returns>
        public string GetEnclosedText(string value, bool includeBracket = false, char startBracket = '(', char endBracket = ')')
        {
            return GetEnclosedText(value, startBracket.ToString(), endBracket.ToString(), includeBracket);
        }
```