【问题标题】:BinarySerialization, Static and Singleton二进制序列化、静态和单例
【发布时间】:2018-01-17 14:08:16
【问题描述】:

在让我的班级通过二进制序列化之后,任何对另一个类中断的静态实例的引用。 这个例子应该更好地解释我的意思:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace staticorsingletontest
{
    [System.Serializable]
    public class weapon 
    {
        public string name;
        public weapon (string name)
            { this.name = name; }
    }
    class Program
    {
        public static weapon sword =  new weapon("Sword");
        public static weapon axe = new weapon("Axe");
        static void Main(string[] args)
        {

            byte[] b;
            Dictionary<weapon, int> WarriorSkills = new Dictionary<weapon,int>();
            Dictionary<weapon, int> Des = new Dictionary<weapon,int>();

            WarriorSkills.Add(sword, 10);
            using (MemoryStream ms = new MemoryStream())
            {
                //Serialize
                new BinaryFormatter().Serialize(ms, WarriorSkills);
                b = ms.ToArray();
                //Deserialize
                ms.Flush();
                ms.Write(b, 0, b.Length);
                ms.Seek(0, SeekOrigin.Begin);

                Des = (Dictionary<weapon, int>)new BinaryFormatter().Deserialize(ms);
            }

            Console.WriteLine(WarriorSkills.Keys.ToArray()[0].name + " is a " + Des.Keys.ToArray()[0].name + ", but are they equal? " + (WarriorSkills.Keys.ToArray()[0] == Des.Keys.ToArray()[0]).ToString());

            Console.ReadLine();

            Console.WriteLine("Warrior's Skill with Sword is ", Des[sword]); //wonderful "KeyNotFoundException" error

            Console.ReadLine();
        }
    }    
}

程序抛出一个错误,因为反序列化的“剑”不是同一个“剑”(它的static,这是怎么回事?)

weapon 设置为singleton 是行不通的,因为那样剑和斧头就是一回事了。

有没有办法指出两把剑是同一件事,或者我没有得到static类的一些核心逻辑?

【问题讨论】:

    标签: c# serialization static singleton binary-serialization


    【解决方案1】:

    如果你反序列化一个(最初是单例的)对象,它无论如何都会是一个新实例,除非你指定反序列化应该返回一个“众所周知的”实例。但是您可以通过一些自定义来做到这一点:

    using System;
    using System.IO;
    using System.Runtime.Serialization;
    using System.Runtime.Serialization.Formatters.Binary;
    
    public class Example
    {
        [Serializable]
        public class Weapon: IObjectReference // here is the trick, see GetRealObject method
        {
            // unless you want to allow to create any kind of weapon I suggest to use an enum for the predefined types
            private enum WeaponKind { Sword, Axe }
    
            public static Weapon Sword { get; } = new Weapon(WeaponKind.Sword);
            public static Weapon Axe { get; } = new Weapon(WeaponKind.Axe);
    
            // this is the only instance field so this will be stored on serialization
            private readonly WeaponKind kind;
    
            public string Name => kind.ToString();
    
            // make the constructor private so no one can create further weapons
            private Weapon(WeaponKind kind)
            {
                this.kind = kind;
            }
    
            // on deserialization ALWAYS a new instance will be created
            // but if you implement IObjectReference, this method will be called before returning the deserialized object
            public object GetRealObject(StreamingContext context)
            {
                // map the temporarily created new deserialized instance to the well-known static member:
                switch (kind)
                {
                    case WeaponKind.Sword:
                        return Sword;
                    case WeaponKind.Axe:
                        return Axe;
                    default:
                        throw new InvalidOperationException("Unknown weapon type");
                }
            }
        }
    }
    

    还有一些测试:

    public static void Main()
    {
        var axe = Weapon.Axe;
        var savedContent = new MemoryStream();
        var formatter = new BinaryFormatter();
        formatter.Serialize(savedContent, axe);
        savedContent.Position = 0;
        var deserializedAxe = (Weapon)formatter.Deserialize(savedContent);
        Console.WriteLine(ReferenceEquals(axe, deserializedAxe)); // prints True
    }
    

    更新:

    如果你所有的武器属性都是不变的(如果有更多的实例应该被认为是相等的也不是问题),那么只需覆盖Equals

    public override bool Equals(object obj)
    {
        var other = obj as Weapon;
        if (other == null)
            return base.Equals(obj);
        return other.kind == this.kind;
    }
    

    如果您覆盖Equals,您也必须覆盖GetHashCode,否则,您将无法在字典中找到同一对象的不同实例:

    public override int GetHashCode()
    {
        return kind.GetHashCode();
    }
    

    请注意== 运算符仍将返回引用相等。如果你想覆盖它,你需要重载 ==!= 操作符:

    public static bool operator ==(Weapon w1, Weapon w2)
    {
        return Equals(w1, w2);
    }
    
    public static bool operator !=(Weapon w1, Weapon w2)
    {
        return !Equals(w1, w2);
    }
    

    【讨论】:

    • 嗯,这让我想到,可以通过覆盖武器的 Equals() 方法来完成吗?
    • 当然,这是最简单的解决方案。但是你的武器不再是单件了。如果每个武器属性都是恒定的,这不是问题。如果他们可以改变,那么,我会继续提出建议的解决方案。
    • 不,它们将是只读的,但是将它们作为非单例有点蹩脚...知道我是否应该覆盖 Equals、RefernceEquals 或 == 运算符以进行字典搜索吗?
    • ReferenceEquals 不能被覆盖。两个对象是否相同的引用,句号。使用建议的解决方案会强制公共可访问对象的引用相等,因此您无需覆盖Equals。解决方案 B:使用Equals(很快)查看我的更新答案
    • 谢谢,这正是我想要的!
    【解决方案2】:

    也许解决这个问题的最佳方法是完全避免序列化武器。

    您可以改为序列化武器密钥,并使用类型安全的枚举来查找您的武器。

    但是,在使用 .net 二进制序列化作为持久性机制之前,您应该考虑所有其他选项 - 它的性能非常差,并且有大量的陷阱会真正伤害到您 - 特别是如果您尝试维护版本向后兼容。

    [Serializable]
    public sealed class Warrior
    {
        private readonly Dictionary<int, int> weaponSkills = new Dictionary<int, int>();
    
        public void SetWeaponSkill(Weapon weapon, int skill)
        {
            weaponSkills[weapon.Key] = skill;
        }
    
        public int GetWeaponSkill(Weapon weapon)
        {
            int skill;
            if (!weaponSkills.TryGetValue(weapon.Key, out skill))
            {
                throw new ArgumentException("Warrior doesn't have weapon");
            }
            return skill;
        }
    }
    
    public static class Weapons
    {
        public static readonly Weapon Sword = new Weapon(1, "Sword");
    
        public static readonly Weapon Dagger = new Weapon(2, "Dagger");
    }
    
    public sealed class Weapon
    {
        public Weapon(int key, string text)
        {
            Key = key;
            Text = text;
        }
    
        public int Key { get; }
        public string Text { get; }
    }
    

    【讨论】:

    • 我需要能够动态创建武器,所以我想我应该找到一些方法来无误地键入它们。
    • 如果您需要动态创建它们,那么您需要序列化一个单例武器库。
    猜你喜欢
    • 2014-01-04
    • 1970-01-01
    • 2011-06-08
    • 1970-01-01
    • 1970-01-01
    • 2019-05-21
    • 2016-07-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多