ぼやきごと/2013-03-14/C#:サロゲートペアを考慮した文字の列挙に foreach を使えるようにする のバックアップ差分(No.1)


  • 追加された行はこの色です。
  • 削除された行はこの色です。
#blog2navi()
*C#:サロゲートペアを考慮した文字の列挙に foreach を使えるようにする [#b0061d58]

C#で文字列を1文字ずつ処理する場合、何も考えなければ次のような感じで書くと思います。

#code(csharp){{
string text = "あいうえお";
foreach (var c in text)
{
    // c を処理する
}
}}

しかしこれだと文字列にサロゲートペア(4バイトで表されるUnicode文字)が含まれている場合に正常な処理が行えません。~
なのでサロゲートペアを考慮した列挙を行う場合は @code{System.Globalization.StringInfo}; クラスの静的メソッド @code{GetTextElementEnumerator}; を使って取得した @code{TextElementEnumerator}; オブジェクトを用いて処理することになります。

- [[文字列を1文字ずつ解析するには?(サロゲート文字対応)[C#、VB] − @IT>http://www.atmarkit.co.jp/fdotnet/dotnettips/732parsechars/parsechars.html]]

#code(csharp){{
string text = "あいうえお";
var e = StringInfo.GetTextElementEnumerator(text);
while (e.MoveNext())
{
    string s = e.GetTextElement();
    if (s.Length == 1)
    {
        // 通常文字を処理
        char c = s[0];
    }
    else
    {
        // サロゲートペアを処理
    }
}
}}

しかし私はこのメソッドを使うたびに思うのです。~
「@code{IEnumerator}; じゃなくて @code{IEnumerable}; なオブジェクトを返してくれよ!」と。~
なぜなら、そうすれば @code{foreach}; なり各種拡張メソッドなりが利用できるからです。

そういうわけでこんなクラスを作りました。

#code(csharp){{
using System;
using System.Collections;
using System.Globalization;

namespace ruche.util
{
    /// <summary>
    /// 文字列に対する TextElementEnumerator 列挙子を公開するクラス。
    /// </summary>
    public class TextElementEnumerable : IEnumerable
    {
        /// <summary>
        /// コンストラクタ。
        /// </summary>
        /// <param name="source">列挙対象となる文字列。</param>
        public TextElementEnumerable(string source)
        {
            this.Source = source;
        }

        /// <summary>
        /// 列挙対象文字列を取得する。
        /// </summary>
        public string Source { get; private set; }

        /// <summary>
        /// TextElementEnumerator 列挙子を取得する。
        /// </summary>
        /// <returns>TextElementEnumerator 列挙子。</returns>
        public TextElementEnumerator GetEnumerator()
        {
            return StringInfo.GetTextElementEnumerator(this.Source);
        }

        #region IEnumerable の明示的実装

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion
    }
}
}}

このクラスを使えば次のように記述できます。

#code(csharp){{
string text = "あいうえお";
foreach (string s in new TextElementEnumerable(text))
{
    if (s.Length == 1)
    {
        // 通常文字を処理
        char c = s[0];
    }
    else
    {
        // サロゲートペアを処理
    }
}
}}

すっきり!

RIGHT:Category: &#x5b;[[プログラミング>ぼやきごと/カテゴリ/プログラミング]]&#x5d;&#x5b;[[C#>ぼやきごと/カテゴリ/C#]]&#x5d; - 2013-03-14 05:07:34
----
RIGHT:&blog2trackback();
#comment(above)
#blog2navi()