You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无法从XML反序列化对象集合——游戏存档加载报错求助

游戏存读档系统反序列化XmlException问题排查

我正在为游戏开发一套存读档系统,目标是将Dictionary<string, object>转换为加密字符串写入磁盘,并能反向读取还原。保存流程(获取可存档数据、序列化、加密、写入磁盘)完全顺畅,但加载时的反序列化阶段抛出异常,无法正确还原状态字典。

保存相关代码

Save方法与数据准备

public void Save()
{
    PrepareSavableData();

    using StreamWriter writer = new(SavegamePath);

    string rawData = AuxMath.SerializeObjectToString(_storedStates);
    string encodedData = AuxMath.Encode(rawData, PublicKey, PrivateKey);

    writer.Write(encodedData);
    writer.Close();

    SavegameCompleted?.Invoke(this, EventArgs.Empty);
}

private void PrepareSavableData()
{
    foreach (var entity in _registeredEntities)
    {
        _storedStates[entity.ID] = entity.GetState();
    }
}

获取实体状态

public object GetState()
{
    List<object> states = new(_savables.Count);

    foreach (var savable in _savables)
    {
        states.Add(savable.CaptureState());
    }

    return states;
}

序列化与加密辅助方法

public static string SerializeObjectToString(object obj)
{
    using MemoryStream memoryStream = new();
    using StreamReader reader = new(memoryStream, Encoding.UTF8);
    DataContractSerializer serializer = new(obj.GetType());

    serializer.WriteObject(memoryStream, obj);
    memoryStream.Position = 0;

    return reader.ReadToEnd();
}

public static string Encode(string input, string publicKey, string privateKey)
{
    try
    {
        byte[] inputAsBytes = Encoding.UTF8.GetBytes(input);
        byte[] publicKeyAsBytes = Encoding.UTF8.GetBytes(publicKey);
        byte[] privateKeyAsBytes = Encoding.UTF8.GetBytes(privateKey);

        using DESCryptoServiceProvider cryptoServiceProvider = new();
        using MemoryStream memoryStream = new();
        using CryptoStream cryptoStream = new(memoryStream,
                                              cryptoServiceProvider.CreateEncryptor(publicKeyAsBytes, privateKeyAsBytes),
                                              CryptoStreamMode.Write);

        cryptoStream.Write(inputAsBytes, 0, inputAsBytes.Length);
        cryptoStream.FlushFinalBlock();

        return Convert.ToBase64String(memoryStream.ToArray());
    }
    catch (Exception e)
    {
        return e.Message;
    }
}

加载相关代码

Load方法与解密

private bool Load()
{
    if (File.Exists(SavegamePath))
    {
        try
        {
            using StreamReader reader = new(SavegamePath);

            string encodedData = reader.ReadToEnd();
            string decodedData = AuxMath.Decode(encodedData, PublicKey, PrivateKey);

            _storedStates = AuxMath.DeserializeStringToObject<Dictionary<string, object>>(decodedData, _knownSavableTypes);
            SavegameLoadSuccesful?.Invoke(this, EventArgs.Empty);

            return true;
        }
        catch (Exception e)
        {
            SavegameLoadFailed?.Invoke(this, new SavegameLoadFailedEventArgs(e.Message));

            return false;
        }
    }

    return false;
}

public static string Decode(string encodedInput, string publicKey, string privateKey)
{
    try
    {
        byte[] encodedInputAsBytes = Convert.FromBase64String(encodedInput);
        byte[] publicKeyAsBytes = Encoding.UTF8.GetBytes(publicKey);
        byte[] privateKeyAsBytes = Encoding.UTF8.GetBytes(privateKey);

        using DESCryptoServiceProvider cryptoServiceProvider = new();
        using MemoryStream memoryStream = new();
        using CryptoStream cryptoStream = new(memoryStream,
                                              cryptoServiceProvider.CreateDecryptor(publicKeyAsBytes, privateKeyAsBytes),
                                              CryptoStreamMode.Write);

        cryptoStream.Write(encodedInputAsBytes, 0, encodedInputAsBytes.Length);
        cryptoStream.FlushFinalBlock();

        return Encoding.UTF8.GetString(memoryStream.ToArray());
    }
    catch (Exception e)
    {
        return e.Message;
    }
}

出问题的反序列化方法

public static T DeserializeStringToObject<T>(string objectAsXml, IEnumerable<Type> knownTypes)
{
    using MemoryStream memoryStream = new();
    using StreamWriter writer = new(memoryStream, Encoding.UTF8);
    DataContractSerializer deserializer = new(typeof(T), knownTypes);

    writer.Write(objectAsXml);
    memoryStream.Position = 0;

    if (deserializer.ReadObject(memoryStream) is T value)
    {
        return value;
    }
    else
    {
        throw new Exception("Passed data is invalid or corrupted and cannot be properly restored!");
    }
}

错误信息

XmlException: Unexpected end of file. Following elements are not closed: Value, KeyValueOfstringanyType, ArrayOfKeyValueOfstringanyType. Line 1, position 197.

问题原因与修复方案

核心原因

StreamWriter内部存在缓冲区,调用Write方法后,字符串数据并未立即全部写入MemoryStream,此时直接设置memoryStream.Position = 0并读取,只能获取到部分XML内容,导致XML结构不完整,触发未结束的异常。

修复方案1:刷新StreamWriter缓冲区

在写入字符串后,调用writer.Flush()强制将缓冲区数据写入内存流:

public static T DeserializeStringToObject<T>(string objectAsXml, IEnumerable<Type> knownTypes)
{
    using MemoryStream memoryStream = new();
    using StreamWriter writer = new(memoryStream, Encoding.UTF8);
    DataContractSerializer deserializer = new(typeof(T), knownTypes);

    writer.Write(objectAsXml);
    writer.Flush(); // 强制刷新缓冲区
    memoryStream.Position = 0;

    if (deserializer.ReadObject(memoryStream) is T value)
    {
        return value;
    }
    else
    {
        throw new Exception("Passed data is invalid or corrupted and cannot be properly restored!");
    }
}

修复方案2:直接将字符串转为字节数组写入内存流

绕过StreamWriter的缓冲机制,直接将字符串编码为字节数组初始化MemoryStream:

public static T DeserializeStringToObject<T>(string objectAsXml, IEnumerable<Type> knownTypes)
{
    byte[] xmlBytes = Encoding.UTF8.GetBytes(objectAsXml);
    using MemoryStream memoryStream = new(xmlBytes);
    DataContractSerializer deserializer = new(typeof(T), knownTypes);

    if (deserializer.ReadObject(memoryStream) is T value)
    {
        return value;
    }
    else
    {
        throw new Exception("Passed data is invalid or corrupted and cannot be properly restored!");
    }
}

额外验证建议

在Load方法中输出decodedData的内容或长度,确认解密后的XML字符串是完整的,排除解密过程中数据丢失的可能性。

内容的提问来源于stack exchange,提问作者Heavy Violence

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 03:48:23