かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

FileMode.OpenOrCreateではまった

今Windows Phone 7向けのアプリケーションをちまちまと作っているのですが、普段使わないファイルへの読み書きをやったらはまってしまったのでメモです。アプリケーションのデータを永続化するときにSQL CEが使えるのですが、そんなに大量データじゃないので私のアプリではDataContractSerializerを使って分離ストレージにファイルで書きだしてました。その部分のコードが以下のような感じです。

using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
    using (var stream = store.OpenFile(QueriesFileName, FileMode.OpenOrCreate))
    {
        var queries = this.MainPage.Queries.Select(q => q.Model);
        // この中でDataContractSerializer使ってる
        SearchQuery.Save(queries, stream);
    }
}

やらかしてたのはOpenFileでFileMode.OpenOrCreateを指定してた所でした。自分の中では、古いファイルの内容は綺麗さっぱり捨て去られて新たなデータを書くつもりだったんですが、ファイルが存在するときはOpenしちゃうので中身残ってるんですよね・・・。


同じ問題を再現するコードはこんな感じで作れます。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;

namespace FileIOTest
{
    class Program
    {
        static void Main(string[] args)
        {
            // 5件のデータをファイルへ書き込む
            {
                var people = Enumerable.Range(1, 5)
                    .Select(i => new Person { Name = "Taro " + i })
                    .ToList();
                using (var fs = new FileStream("sample.xml", FileMode.OpenOrCreate))
                {
                    var s = new DataContractSerializer(typeof(IEnumerable<Person>));
                    s.WriteObject(fs, people);
                }
            }

            // 1件のデータをファイルへ書き込む
            {
                var people = Enumerable.Range(1, 1)
                    .Select(i => new Person { Name = "Taro " + i })
                    .ToList();
                using (var fs = new FileStream("sample.xml", FileMode.OpenOrCreate))
                {
                    var s = new DataContractSerializer(typeof(IEnumerable<Person>));
                    s.WriteObject(fs, people);
                }
            }

            // 結果のファイルを画面に表示
            Console.WriteLine(File.ReadAllText("sample.xml"));
        }
    }

    // シリアライズ対象のデータ
    public class Person
    {
        public string Name { get; set; }
    }
}

5件ぶんのデータをファイルに書いたあと、同じファイルに5件より少ない1件ぶんのデータを書きだしています。こうするとsample.xmlは以下のような内容になっています。(見やすいように整形しています)

<ArrayOfPerson 
    xmlns="http://schemas.datacontract.org/2004/07/FileIOTest" 
    xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <Person>
        <Name>Taro 1</Name>
    </Person>
</ArrayOfPerson>ro 2</Name></Person><Person> <!-- ここでおかしいことに! -->
    <Name>Taro 3</Name>
</Person><Person>
    <Name>Taro 4</Name>
</Person><Person>
    <Name>Taro 5</Name>
</Person></ArrayOfPerson>

前のデータが残ってるところに前より短いデータを書きこむことでファイルがぐちゃぐちゃになってしまってます。当然このファイルをデシリアライズしようとすると例外が飛んでしまいます。


ということで正しくはFileMode.OpenOrCreateではなくFileMode.Createを指定しましょう。ちょっとはまったのでメモメモ。