かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

StringInfoのSubstringByTextElementsがWindowsストアアプリでは使えない

がりっちさんに4バイト文字の?(とびうお)を送り付けて、がりっちさんが作ってるTwitterクライアントの文字列処理をばぐらせて遊んでいたら、?(とびうお)問題という名前がつきました。

StringInfoクラス使えば便利メソッドあるから簡単じゃん?と思ったら

SubstringByTextElementsという、今回のがりっちさんの中で重要なメソッドがなくなってたみたいです。自力実装されてたコードを見て自分だったら…というのを書いてみました。

public static class StringExtensions
{
    /// <summary>
    /// 文字数を返す
    /// </summary>
    /// <param name="self">対象の文字列</param>
    /// <returns>文字数</returns>
    public static int LengthInTextElements(this string self)
    {
        return new StringInfo(self).LengthInTextElements;
    }

    /// <summary>
    /// 1文字ずつ返す感じのIEを作る
    /// </summary>
    /// <param name="self">対象の文字列</param>
    /// <param name="index">開始位置</param>
    /// <returns>IE</returns>
    public static IEnumerable<string> GetTextElementEnumerable(this string self, int index = 0)
    {
        var e = StringInfo.GetTextElementEnumerator(self, index);
        while (e.MoveNext())
        {
            yield return (string)e.Current;
        }
    }

    /// <summary>
    /// 指定した範囲の文字列を切り出す
    /// </summary>
    /// <param name="self">対象の文字列</param>
    /// <param name="startIndex">開始位置</param>
    /// <param name="length">長さ</param>
    /// <returns></returns>
    public static string SubstringByTextElements(this string self, int startIndex, int length)
    {
        var sub = self.GetTextElementEnumerable(startIndex).Take(length).ToArray();
        if (sub.Length != length)
        {
            throw new ArgumentOutOfRangeException();
        }

        return string.Concat(sub);
    }
}

とりあえずGetTextElementEnumeratorというメソッドがあるので、それを使ってIEにするメソッドを作ってしまえばSubstringなんて楽勝さ!という発想。以下のテストケースを通してみてグリーンでした。

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void LengthInTextElements()
    {
        var target = "俺が?だ!";
        Assert.AreEqual(5, target.LengthInTextElements());
    }

    [TestMethod]
    public void GetTextElementEnumerable()
    {
        var target = "俺が?だ!";
        var result = target.GetTextElementEnumerable().ToArray();
        Assert.AreEqual("俺", result[0]);
        Assert.AreEqual("が", result[1]);
        Assert.AreEqual("?", result[2]);
        Assert.AreEqual("だ", result[3]);
        Assert.AreEqual("!", result[4]);
    }

    [TestMethod]
    public void SubString()
    {
        var target = "俺が?だ!";
        var result = target.SubstringByTextElements(2, 1);
        Assert.AreEqual("?", result);
    }

    [TestMethod]
    public void SubString_範囲外()
    {
        try
        {
            var target = "俺が?だ!";
            var result = target.SubstringByTextElements(2, 1000);
            Assert.Fail();
        }
        catch (Exception ex)
        {
            // Lengthがはみ出てると例外が出る
            Assert.IsInstanceOfType(ex, typeof(ArgumentOutOfRangeException));
        }
    }
}