【问题标题】:How avoid exception deserializing an invalid enum item?如何避免反序列化无效枚举项的异常?
【发布时间】:2025-12-27 07:55:16
【问题描述】:

为了简单起见,我将使用水果来展示我的示例代码。实际上,我正在做一些更有意义的事情(我们希望如此)。假设我们有一个枚举:

public enum FruitType
{
    Apple,
    Orange,
    Banana
}

还有一个班级:

[Serializable]
public class Fruit
{
    public FruitType FruitType { get; set; }
    public Fruit(FruitType type)
    {
        this.FruitType = type;
    }
}

我们可以对其进行序列化和反序列化。现在,让我们修改枚举,使其成为现在:

public enum FruitType
{
    GreenApple,
    RedApple,
    Orange,
    Banana
}

当反序列化先前序列化的对象时,您会收到 System.InvalidOperation 异常,因为 Apple(原始枚举项)无效。该对象不会被反序列化。

我能够解决此问题的一种方法是在序列化时为Fruit 类中的FruitType 属性指定一个不同的名称,如下所示:

    [XmlElement(ElementName = "Mode")]
    public FruitType FruitType { get; set; }

现在,在反序列化过程中,旧属性会因为找不到而被忽略。我想知道是否有办法在反序列化期间忽略/跳过无效的枚举项,这样就不会引发异常并且对象仍然会被反序列化。

【问题讨论】:

  • 查看我对包含工作示例项目的链接问题的回答:*.com/a/10709040

标签: c# serialization xml-serialization deserialization xml-deserialization


【解决方案1】:

留下Apple 并用ObsoleteAttribute 标记它。这样,任何使用Apple 的代码都会产生编译器警告。

【讨论】:

  • 我在“Apple”枚举项上方添加了 [Obsolete]。但是,在反序列化和较旧的对象时,我仍然会遇到以下异常:实例验证错误:'Apple' is not a valid value for FruitType.
【解决方案2】:

我自己一直在寻找类似的答案,并编写此代码以在 XML 包含无效枚举值时捕获异常。它删除该元素,并尝试再次反序列化。如果元素是必需的,你仍然会得到一个异常。它不完美,但应该能让你大部分时间到达你想去的地方

    private const string XmlError = "There is an error in XML document ";
    private const string InstanceValidationError = "Instance validation error:";
    private static readonly Regex XmlErrorRegex = new Regex("There is an error in XML document \\((\\d+), (\\d+)\\).");
    private static readonly Regex InstanceValidationErrorRegex = new Regex("Instance validation error: '(\\S+)' is not a valid value for (\\S+).");
    private const string TagFinderString = "\\>{0}\\</(\\S+)\\>";

    /// <summary>
    /// Helper method to deserialize xml message
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="message"></param>
    /// <returns></returns>
    public T Deserialize(string message)
    {
        var result = default(T);
        if (!string.IsNullOrEmpty(message))
        {
            using (var reader = new StringReader(message))
            {
                try
                {
                    result = (T)_serializer.Deserialize(reader);
                }
                catch (InvalidOperationException ex)
                {
                    if (ex.Message.StartsWith(XmlError))
                    {
                        if(ex.InnerException != null && ex.InnerException.Message.StartsWith(InstanceValidationError))
                        {
                            var instanceValidationErrorMatches = InstanceValidationErrorRegex.Matches(ex.InnerException.Message);
                            if (instanceValidationErrorMatches.Count > 0)
                            {
                                var locationMatches = XmlErrorRegex.Matches(ex.Message);
                                var startIndex = GetStartIndex(message, locationMatches);
                                var match = instanceValidationErrorMatches[0];
                                if(match.Groups.Count > 0)
                                {
                                    var toRemove = GetToRemove(message, match, startIndex);

                                    return Deserialize(message.Replace(toRemove, string.Empty));
                                }
                            }
                        }
                    }
                }
            }
        }
        return result;
    }

    private static string GetToRemove(string message, Match match, int startIndex)
    {
        var value = match.Groups[1];
        var tagFinder = new Regex(string.Format(TagFinderString, value));
        var tagFinderMatches = tagFinder.Matches(message.Substring(startIndex));
        var tag = tagFinderMatches[0].Groups[1];

        return string.Format("<{0}>{1}</{0}>", tag, value);
    }

    private static int GetStartIndex(string message, MatchCollection locationMatches)
    {
        var startIndex = 0;
        if (locationMatches.Count > 0)
        {
            var lineNumber = int.Parse(locationMatches[0].Groups[1].Value);
            var charIndex = int.Parse(locationMatches[0].Groups[2].Value);
            using (var locationFinder = new StringReader(message))
            {
                for (var i = 1; i < lineNumber; i++)
                {
                    startIndex += locationFinder.ReadLine().Length;
                }
            }
            startIndex += charIndex;
        }
        return startIndex;
    }

【讨论】:

  • 谢谢。我不喜欢这个解决方案,但除了完全改变序列化之外找不到更好的解决方案。
【解决方案3】:

我发布了一个similar question 并没有找到一个简单的方法来捕获反序列化器在 XML 文件中遇到 Apple 时引发的异常。我可以在反序列化过程中捕获许多其他异常,以查找缺少的属性或元素,但不能捕获无效的枚举值。无效的枚举值(在您的情况下为 Apple)使我脱离了反序列化。

一种可能的解决方案是在 Fruit 类上实现 IXMLSerializable。当反序列化程序调用IXMLSerailizable.ReadXML() 方法时,您必须查看传递给您的内容。当值为“Apple”时,根据某些逻辑将枚举设置为 GreenApple 或 RedApple。

【讨论】: