【问题标题】:Custom serialization of an object in .NET.NET 中对象的自定义序列化
【发布时间】:2015-06-17 23:41:23
【问题描述】:

我需要将对象列表序列化为平面文件。调用将类似于:

class MyObject
{
    public int x;
    public int y;
    public string a;
    public string b;
}

当我序列化这个对象时,一条记录应该写在一个 ascii 编码的平面文件中。现在,字段 x 的长度应该是 10 个字符(右对齐),字段 y 应该是 20 个字符(右对齐),字段 a 应该是 40(左对齐),字段 b 应该是 100 个字符(左对齐)。 我怎样才能做到这样的事情。

序列化对象应如下所示:

        25                   8                                     akjsrj                                                                                          jug

我在想也许我可以将自定义属性属性应用于字段,并可以在运行时决定如何序列化字段..

【问题讨论】:

    标签: .net serialization


    【解决方案1】:

    这是一个使用普通旧反射和自定义属性的解决方案。它只会对每个文件进行序列化/反序列化一项,但您可以轻松地为每个文件添加对多个项目的支持。

    // Attribute making it possible
    public class FlatFileAttribute : Attribute
    {
        public int Position { get; set; }
        public int Length { get; set; }
        public Padding Padding { get; set; }
    
        /// <summary>
        /// Initializes a new instance of the <see cref="FlatFileAttribute"/> class.
        /// </summary>
        /// <param name="position">Each item needs to be ordered so that 
        /// serialization/deserilization works even if the properties 
        /// are reordered in the class.</param>
        /// <param name="length">Total width in the text file</param>
        /// <param name="padding">How to do the padding</param>
        public FlatFileAttribute(int position, int length, Padding padding)
        {
            Position = position;
            Length = length;
            Padding = padding;
        }
    }
    
    public enum Padding
    {
        Left,
        Right
    }
    
    
    /// <summary>
    /// Serializer making the actual work
    /// </summary>
    public class Serializer
    {
        private static IEnumerable<PropertyInfo> GetProperties(Type type)
        {
            var attributeType = typeof(FlatFileAttribute);
    
            return type
                .GetProperties()
                .Where(prop => prop.GetCustomAttributes(attributeType, false).Any())
                .OrderBy(
                    prop =>
                    ((FlatFileAttribute)prop.GetCustomAttributes(attributeType, false).First()).
                        Position);
        }
        public static void Serialize(object obj, Stream target)
        {
            var properties = GetProperties(obj.GetType());
    
            using (var writer = new StreamWriter(target))
            {
                var attributeType = typeof(FlatFileAttribute);
                foreach (var propertyInfo in properties)
                {
                    var value = propertyInfo.GetValue(obj, null).ToString();
                    var attr = (FlatFileAttribute)propertyInfo.GetCustomAttributes(attributeType, false).First();
                    value = attr.Padding == Padding.Left ? value.PadLeft(attr.Length) : value.PadRight(attr.Length);
                    writer.Write(value);
                }
                writer.WriteLine();
            }
        }
    
        public static T Deserialize<T>(Stream source) where T : class, new()
        {
            var properties = GetProperties(typeof(T));
            var obj = new T();
            using (var reader = new StreamReader(source))
            {
                var attributeType = typeof(FlatFileAttribute);
                foreach (var propertyInfo in properties)
                {
                    var attr = (FlatFileAttribute)propertyInfo.GetCustomAttributes(attributeType, false).First();
                    var buffer = new char[attr.Length];
                    reader.Read(buffer, 0, buffer.Length);
                    var value = new string(buffer).Trim();
    
                    if (propertyInfo.PropertyType != typeof(string))
                        propertyInfo.SetValue(obj, Convert.ChangeType(value, propertyInfo.PropertyType), null);
                    else
                        propertyInfo.SetValue(obj, value.Trim(), null);
                }
            }
            return obj;
        }
    
    }
    

    还有一个小演示:

    // Sample class using the attributes
    public class MyObject
    {
        // First field in the file, total width of 5 chars, pad left
        [FlatFile(1, 5, Padding.Left)]
        public int Age { get; set; }
    
        // Second field in the file, total width of 40 chars, pad right
        [FlatFile(2, 40, Padding.Right)]
        public string Name { get; set; }
    }
    
    private static void Main(string[] args)
    {
        // Serialize an object
        using (var stream = File.OpenWrite("C:\\temp.dat"))
        {
            var obj = new MyObject { Age = 10, Name = "Sven" };
            Serializer.Serialize(obj, stream);
        }
    
        // Deserialzie it from the file
        MyObject readFromFile = null;
        using (var stream = File.OpenRead("C:\\temp.dat"))
        {
            readFromFile = Serializer.Deserialize<MyObject>(stream);
        }
    
    }
    

    【讨论】:

    • 有关此代码的一些快速说明。您没有在反序列化中使用 Position 属性。此外,如果您添加一些东西来检查属性的存在,您可以选择为您的类的每个属性提供一个属性。否则,这太棒了,真的帮助了我。
    • 我直接在答案中写了它只是为了说明您如何自己做。我从来没有打算创建一个完整的工作示例。
    • 我明白了。嗯,它非常接近。我现在正试图弄清楚当我的平面文件中有一个 char 并想在我的类中相应地设置一个 Enum 时该怎么做(从 DB2 大型机到 SQLServer 上的 .NET)
    【解决方案2】:

    是的,您可以通过添加自定义属性并创建自己的序列化程序来实现这一点。

    本文提供了创建自定义二进制序列化程序的示例。

    http://www.codeproject.com/KB/dotnet/CustomSerializationPart2.aspx

    【讨论】:

    • 在编辑问题之前,文件格式 DID 看起来是二进制的。请重新考虑您的反对意见。
    【解决方案3】:

    对不起,我看错了你的问题。我虽然您正在寻找可以自己处理序列化的属性。

    当然,您可以创建自己的属性并通过反射处理自己的序列化。如果我这样做,那将是首选的解决方案。我更喜欢它,因为有属性:

    • 您可以指定项目的顺序。
    • 您可以指定字段的长度。

    至于具体的实现,简单的反射和字符串格式化就可以了。

    旧答案: 这是一个非常具体的场景。所以我不相信有任何 .NET 功能可以让 .NET 处理它。

    但是像这样的硬编码序列化和反序列化不应该超过20行代码..

    【讨论】:

      【解决方案4】:

      这种格式是固定的吗?如果您对如何格式化输出有意见,我强烈建议您使用 protobuf-net。这是一个非常快的库,它将对您的对象使用二进制序列化方法,开销最小,并且(我重复自己)令人难以置信的性能。该协议是 google 专门针对这些优点而发明的。

      如果您无法更改格式,您可以创建自定义属性并在运行时将其读出。但请记住,反射可能会有点慢,具体取决于您的序列化需求。如果您只有一种类型的对象,也许最好提供一个特殊的序列化服务,将属性直接写入文件。

      链接: http://code.google.com/p/protobuf-net/

      【讨论】:

      • 这种固定宽度的格式最好使用字符串格式处理,printfString.Format 视情况而定。除非有 很多 个不同的类要格式化,否则自定义属性是多余的。 Protobuf 在这里不合适。
      【解决方案5】:

      没有用于平面文件的特殊序列化程序。使用string formatting 和操作函数,例如String.Format ("{0,10}{1,-20}{2,-40}{3,-100}", x, y, a, b) 应该根据您的格式生成一行。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-19
        • 2020-07-05
        相关资源
        最近更新 更多