首页 > 解决方案 > 处理集合和接口的 XML 序列化的通用解决方案

问题描述

我在五十个基础上发布这个。

50% 的可能性我遗漏了一些明显的东西,其他人会告诉我为什么我是个傻瓜——在这种情况下,这可以更简单地完成吗?如果你愿意,告诉我我错了——但请不要咬得太紧!

50% 我的解决方案实际上是有用的,其他人可能会喜欢它。

不管怎样,我希望能学到一些东西。

我正在将 XML 序列化用于一个非常繁重的项目,其中我有一条分析生产线。生产线有很多步骤,所以我写了一些非常抽象的基础机械来作为它们的基础。已经做了很多工作!60k 行代码。1,000 种类型。

我现在要做的是将生产线的任何部分序列化为 XML,而不必为生产线中的每个组件编写自定义序列化代码。我想要一个单一的方法,在一个基类中。到目前为止,这没有什么不寻常的。

但告密者是 C#(嗯,.NET)

  1. 无法序列化字典
  2. 无法序列化接口
  3. 在 XML 序列化中提供有限的调试、日志记录和可检查性。

所以我在我的基类中编写了以下函数,一切都继承。

    public void WriteXml(XmlWriter writer)
    {
        Console.WriteLine("############### WRITING XML FOR " + GetType() + " " + this.Name);

        foreach (FieldInfo fieldInfo in this.GetFieldInfos(this))
        {
            try
            {
                string fieldName  = fieldInfo.Name;
                var fieldValue = fieldInfo.GetValue(this);
                if (!IsBackFieldName(fieldName))
                {
                    Console.WriteLine("Serializing\t" + fieldInfo.FieldType + "\t\t" + fieldName + "\t" + fieldValue);
                    if (fieldInfo.FieldType.IsDictionary())
                    {
                        // TODO intercept any Dictionary type and convert to SerializableDictionary
                        ;
                    }
                    writer.WriteStartElement(fieldName);
                    if (fieldInfo.FieldType.IsXmlSerializable())
                    {
                        XmlSerializer xmlSerializer;
                        if (fieldInfo.FieldType.IsInterface)
                        {
                            // look through the interface to the underlying type, which will have a parameterless constructor for serialization
                            IData castedValue      = fieldValue as IData;
                            Type  lookThroughClass = castedValue.SerializationType;
                            xmlSerializer = new XmlSerializer(lookThroughClass);
                        }
                        else
                            xmlSerializer = new XmlSerializer(fieldInfo.FieldType);
                        // serialization here can be built-in or overriden if IXmlSerializable is implemented
                        xmlSerializer.Serialize(writer, fieldValue);
                    }
                    else
                    {
                        writer.WriteComment("Not serializable " + fieldInfo.FieldType);
                    }
                    writer.WriteEndElement();
                }
                else
                {
                    // skip backing field
                    Console.WriteLine("SKIPPING\t" + fieldInfo.FieldType + "\t\t" + fieldName + "\t" + fieldValue);
                    ;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Error writing XML: " + e.Message);
            }
        }

        foreach (PropertyInfo propertyInfo in GetPropertyInfos(this))
        {
            try
            {
                string propertyName = propertyInfo.Name;
                var propertyValue = propertyInfo.GetValue(this);
                if (!IsBackFieldName(propertyName))
                {
                    Console.WriteLine("Serializing\t" + propertyInfo.PropertyType + "\t\t" + propertyName + "\t" + propertyValue);
                    // TODO intercept any Dictionary type and convert to SerializableDictionary
                    if (propertyInfo.PropertyType.IsDictionary())
                    {
                        // TODO intercept any Dictionary type and convert to SerializableDictionary
                        ;
                    }
                    writer.WriteStartElement(propertyName);
                    if (propertyInfo.PropertyType.IsXmlSerializable())
                    {
                        XmlSerializer xmlSerializer;
                        if (propertyInfo.PropertyType.IsInterface)
                        {
                            // look through the interface to the underlying type, which will have a parameterless constructor for serialization
                            IData castedValue      = propertyValue as IData;
                            Type  lookThroughClass = castedValue.SerializationType;
                            xmlSerializer = new XmlSerializer(lookThroughClass);
                        }
                        else
                            xmlSerializer = new XmlSerializer(propertyInfo.PropertyType);

                        // serialization here can be built-in or overriden if IXmlSerializable is implemented
                        xmlSerializer.Serialize(writer, propertyValue);
                    }
                    else
                    {
                        writer.WriteComment("Not serializable " + propertyInfo.PropertyType);
                    }
                    writer.WriteEndElement();
                }
                else
                {
                    // skip backing field
                    Console.WriteLine("SKIPPING\t" + propertyInfo.PropertyType + "\t\t" + propertyName + "\t" + propertyValue);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Error writing XML: " + e.Message);
            }

        }

        return;
    }

支持的基础设施是:

public interface IData
    : IXmlSerializable
{
    #region  Members

    string   Name     { get; }

    #endregion

    #region Methods

    /// <summary>
    /// Allows us to pass the underlying type back out through an interface.
    /// We have to know the actual type for serialization, because interfaces cannot be instantiated.
    /// </summary>
    Type SerializationType { get; }

    #endregion

}

IData 由生产线的所有成员实现,它们也继承了基类中的 XML 写入和读取方法。关键(可能是偷偷摸摸的)部分是每个子类都必须SerializationType在本地实现:

    public Type SerializationType => GetType();

这允许本地范围的类型在界面中可见。

这些是 main 方法中的辅助函数:

    public PropertyInfo[] GetPropertyInfos(object theObject)
    {
        // https://stackoverflow.com/questions/6536163/how-to-list-all-variables-of-class
        BindingFlags bindingFlags = BindingFlags.Instance  |
                                    BindingFlags.NonPublic |
                                    BindingFlags.Public;
        // Type           theType       = this.GetType();
        Type           theType       = theObject.GetType();
        PropertyInfo[] propertyInfos = theType.GetProperties(bindingFlags);
        return propertyInfos;
    }

    public FieldInfo[] GetFieldInfos(object theObject)
    {
        // https://stackoverflow.com/questions/6536163/how-to-list-all-variables-of-class
        BindingFlags bindingFlags = BindingFlags.Instance  |
                                    BindingFlags.NonPublic |
                                    BindingFlags.Public;
        // Type        theType    = this.GetType();
        Type        theType    = theObject.GetType();
        FieldInfo[] fieldInfos = theType.GetFields(bindingFlags);
        return fieldInfos;
    }

在静态扩展类中:

    public static bool IsXmlSerializable(this Type testType)
    {
        if (testType.IsSerializable)
            return true;
        // just for good measure
        var interfaces = testType.GetInterfaces().ToList();
        if (interfaces.Contains(typeof(IXmlSerializable)))
            return true;
        return false;
    }

所以

  1. 这种方法合理吗?
  2. 可以改进或缩短吗?
  3. 其他人是否已经发布了更好的解决方案?

标签: c#xmlserializationcollectionsinterface

解决方案


推荐阅读