かずきのBlog@hatena

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

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を指定しましょう。ちょっとはまったのでメモメモ。