2

我最近决定使用protobuf-net来序列化我的应用程序使用的大型纯文本数据文件,以查看解析纯文本数据是否有任何性能提升。

数据类:

[ProtoContract]
public class ClimateFile : BaseModel
{
    #region Properties
    [ProtoMember(1)]
    public double Latitude { get { return GetValue(() => Latitude); } private set { SetValue(() => Latitude, value); } }

    [ProtoMember(2)]
    public double Longitude { get { return GetValue(() => Longitude); } private set { SetValue(() => Longitude, value); } }

    [ProtoMember(3)]
    public string Notes { get { return GetValue(() => Notes); } set { SetValue(() => Notes, value); } }

    [ProtoMember(4)]
    public DateTime MinDate { get { return GetValue(() => MinDate); } private set { SetValue(() => MinDate, value); } }

    [ProtoMember(5)]
    public DateTime MaxDate { get { return GetValue(() => MaxDate); } private set { SetValue(() => MaxDate, value); } }

    [ProtoMember(6)]
    public List<ClimateDailyData> DailyData { get { return GetValue(() => DailyData); } private set { SetValue(() => DailyData, value); } }
    #endregion

    #region Method
    public static ClimateFile Load(string filePath)
    {
        try
        {
            var ext = Path.GetExtension(filePath).ToUpper();
            if (ext == ".P51")
                return new ClimateFile(filePath);
            else if (ext == ".P51X")
            {
                ClimateFile climateFile;
                using (var file = File.OpenRead(filePath))
                {
                    climateFile = Serializer.Deserialize<ClimateFile>(file);
                }
                return climateFile;
            }
        }
        catch (Exception e)
        {
            throw new InvalidDataException(String.Format("The file \"{0}\" could not be opened, {1}", filePath, ExceptionUtilities.JoinExceptionMessages(e)));
        }

        throw new ArgumentException("filePath was not a .P51 or .P51X file.");
    }

    public void LoadP51File(string filePath)
    {
        List<string[]> lines = GetFileLines(filePath);
        Latitude = double.Parse(lines[0][0]);
        Longitude = double.Parse(lines[0][1]);
        for (int i = 2; i < lines[0].Length; ++i)
            Notes += String.Format("{0} ", lines[0][i]);
        MinDate = GetDate(lines[2][0]);
        MaxDate = GetDate(lines[lines.Count - 1][0]);

        // Parse daily data
        lines.RemoveRange(0, 2);
        var dailyData = lines.Select(row => new ClimateDailyData()
            {
                DataDate = GetDate(row[0]),
                MaxTemperature = double.Parse(row[2]),
                MinTemperature = double.Parse(row[3]),
                Rainfall = double.Parse(row[4]),
                Evaporation = double.Parse(row[5]),
                SolarRadiation = double.Parse(row[6])
            }).ToList();
        DailyData = dailyData;
    }

    public void SaveP51XFile(string filePath)
    {
        using (var file = File.Create(filePath))
        {
            Serializer.Serialize<ClimateFile>(file, this);
        }
    }

    public List<string[]> GetFileLines(string filePath)
    {
        var rows = new List<string[]>();
        var cells = new List<string>();
        var cell = new StringBuilder();
        using (var fs = new BufferedStream(File.OpenRead(filePath)))
        {
            int buffer;
            while ((buffer = fs.ReadByte()) != -1)
            {
                char nextChar = (char)buffer;

                if (nextChar >= '!') // If the character is a non-whitespace printable character
                {
                    cell.Append(nextChar);
                    continue;
                }

                if (nextChar == ' ' || nextChar == '\t')
                {
                    if (cell.Length > 0)
                    {
                        cells.Add(cell.ToString());
                        cell.Clear();
                    }
                    continue;
                }

                if (nextChar == '\r' || nextChar == '\n')
                {
                    if (cell.Length > 0)
                    {
                        cells.Add(cell.ToString());
                        cell.Clear();
                    }
                    if (cells.Count > 0)
                    {
                        rows.Add(cells.ToArray());
                        cells.Clear();
                    }
                    continue;
                }
                throw new InvalidDataException("The climate file contains unknown characters.");
            }

            if (cell.Length > 0)
                cells.Add(cell.ToString());
            if (cells.Count > 0)
                rows.Add(cells.ToArray());
        }
        return rows;
    }

    public DateTime GetDate(string date)
    {
        return DateTime.ParseExact(date, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None);
    }

    public override void Dispose()
    {
        if (Disposed)
            return;
        if (DailyData != null)
            foreach (ClimateDailyData dailyData in DailyData)
                dailyData.Dispose();
        base.Dispose();
    }
    #endregion

    #region Initialization
    public ClimateFile()
    {
    }

    public ClimateFile(string filePath)
    {
        LoadP51File(filePath);
    }
    #endregion
}

采集数据类:

    [ProtoContract]
public class ClimateDailyData : BaseModel
{
    [ProtoMember(1)]
    public DateTime DataDate { get { return GetValue(() => DataDate); } set { SetValue(() => DataDate, value); } }

    [ProtoMember(2)]
    public int JulianDay { get { return DataDate.DayOfYear; } }

    [ProtoMember(3)]
    public double MaxTemperature { get { return GetValue(() => MaxTemperature); } set { SetValue(() => MaxTemperature, value); } }

    [ProtoMember(4)]
    public double MinTemperature { get { return GetValue(() => MinTemperature); } set { SetValue(() => MinTemperature, value); } }

    [ProtoMember(5)]
    public double Rainfall { get { return GetValue(() => Rainfall); } set { SetValue(() => Rainfall, value); } }

    [ProtoMember(6)]
    public double Evaporation { get { return GetValue(() => Evaporation); } set { SetValue(() => Evaporation, value); } }

    [ProtoMember(7)]
    public double SolarRadiation { get { return GetValue(() => SolarRadiation); } set { SetValue(() => SolarRadiation, value); } }
}

序列化命令:

public ICommand ImportP51
    {
        get
        {
            return new RelayCommand(delegate
                {
                    OpenFileDialog openDialogue = new OpenFileDialog()
                    {
                        AddExtension = true,
                        CheckPathExists = true,
                        CheckFileExists = true,
                        DefaultExt = ".p51",
                        Filter = "Climate Files|*.p51",
                        InitialDirectory = DataPath,
                        Multiselect = false,
                        Title = "Select a File to Open"
                    };

                    if ((bool)openDialogue.ShowDialog())
                    {
                        string filePath = String.Empty;
                        try
                        {
                            filePath = openDialogue.FileName;
                            var climate = new Climate();
                            climate.FilePath = filePath;
                            var savePath = String.Format("{0}\\{1}.p51x", ClimateDataPath, Path.GetFileNameWithoutExtension(filePath));
                            climate.FileData.SaveP51XFile(savePath);
                        }
                        catch (Exception e)
                        {
                            MessageBox.Show(String.Format("The file \"{0}\" could not be opened, {1}", filePath, ExceptionUtilities.JoinExceptionMessages(e)), "Invalid File");
                        }
                    }
                });
        }
    }

执行时ImportP51,出现“不支持指定方法”异常,我使用的是 2012 年 12 月 10 日发布的 protobuf-net V2 r594,如果我无法获得它,我正在考虑尝试使用以前的版本之一使用此版本。有人可以告诉我我做错了什么吗?

4

1 回答 1

3

我试图重建代码的工作版本(因为我没有你的BaseModel),我已经通过使用自动实现的属性并为列表(DailyData)添加了一个初始化程序,并从谷歌代码中明确引用 r594。我得到的唯一错误是:

无法将更改应用于属性 ClimateDailyData.JulianDay

这是有道理的,因为它不能正确地将数据反序列化为只读属性,所以我从那个属性中删除了序列化标记:

// [ProtoMember(2)] removed - serialized implicitly via DataData
public int JulianDay { get { return DataDate.DayOfYear; } }

之后它工作正常。因为我没有你的命令框架,所以我有:

static class Program
{
    static void Main()
    {
        var climateFile = new ClimateFile();
        climateFile.InitSomeDataForSerialization();

        climateFile.SaveP51XFile("foo.P51X");
        var clone = ClimateFile.Load("foo.P51X");
    }
}

其中InitSomeDataForSerialization只设置了一些值(因为他们有私人设置器,我不能这样做Main):

public void InitSomeDataForSerialization()
{
    Longitude = 10; Latitude = 4; Notes = "Test";

    DailyData.Add(
        new ClimateDailyData { DataDate = DateTime.Today, MinTemperature = 12, MaxTemperature = 35}
    );
}

而且......它有效。

凭直觉,我检查了如果您引用“CoreOnly”而不是“Full”会发生什么,但是由于Serializer.Serialize<T>并且Serializer.Deserialize<T>不存在而拒绝编译。

我很乐意提供帮助,但据我所知,没有任何问题。

接下来我建议的事情:

  • 显示此异常中发生的情况的完整堆栈跟踪,包括任何内部异常(请参阅下面的修复)
  • 准确检查您在 594 zip 中引用的文件(查看“What Files Do I Need.txt”,但我猜对您来说正确的是“Full/net30/...”;nuget 包配置最合适的文件自动)
  • 请展示一个完全可重现的示例,即我可以在哪里按下F5并查看确切的异常(我必须更改示例中的代码才能编译,这意味着我不再测试相同的东西)

修复正确的异常包装:

catch (Exception e)
{
    throw new InvalidDataException(String.Format("The file \"{0}\" could not be opened", filePath), e);
}
于 2012-10-23T06:35:27.873 回答