0

After having my class go through binary serialization any references to static instance of another class break. The example should explain better what i mean:

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();
        }
    }    
}

Program throws an error because deserialized "sword" is not the same "sword" (its static, how that even happens?)

Making weapon class a singleton would not work because then sword and axe will be the same thing.

Is there a way to point out that both swords are the same thing, or i dont get some core logic of static classes?

xingbin
  • 27,410
  • 9
  • 53
  • 103
user2998964
  • 99
  • 1
  • 1
  • 9

2 Answers2

1

Perhaps the best way around this is to avoid serializing weapon at all.

You can instead Serialize a weapon key, and have a type safe enumeration that you look up your weapon from.

You should, however, consider every other option before using .net binary serialization as a persistence mechanism - it performs very poorly, and there are a very large number of gotchas that can really hurt you - particularly if you are trying to maintain version backwards compatibility.

[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; }
}
Adam Brown
  • 1,667
  • 7
  • 9
1

If you deserialize an (originally singleton) object it will be a new instance anyway unless you specify that deserialization should return a "well known" instance. But you can do it by some customization:

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");
            }
        }
    }
}

And some test:

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
}

Update:

If all of your weapon properties are constant (and it is not a problem if there are more instances that should be considered to be equal), then just override Equals:

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

If you override Equals you must override GetHashCode as well, otherwise, you will not able to find the different instances of the same object in the dictionary:

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

Please note that == operator will still return reference equality. If you want to override this you need to overload the == and != operators:

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

public static bool operator !=(Weapon w1, Weapon w2)
{
    return !Equals(w1, w2);
}
György Kőszeg
  • 17,093
  • 6
  • 37
  • 65
  • Hmm, that made me think, could it be done by overriding Equals() method of weapon? – user2998964 Jan 17 '18 at 16:17
  • Of course, that's the easiest solution. But then your weapons are not singletons anymore. Which is not a problem if every weapon property is constant. If they can change, well, I would go on with the proposed solution. – György Kőszeg Jan 17 '18 at 16:19
  • Nah, they gonna be readonly, but having them as not singleton is kinda lame... any idea if i should override Equals, RefernceEquals or == operator for Dictionary search? – user2998964 Jan 17 '18 at 16:25
  • `ReferenceEquals` cannot be overridden. Two objects are the same reference or not, period. Using the proposed solution forces reference equality for the publically accessible objects so you don't need to override `Equals`. Solution B: see my updated answer with `Equals` (soon) – György Kőszeg Jan 17 '18 at 16:30
  • Thanks, that is just what i was looking for! – user2998964 Jan 17 '18 at 16:45