Binary Serialization uninitialized array of struct
I have provided a minimal code to mimic the scenario. Here is the code:
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Collections.Generic;
namespace Serialization
{
class Program
{
static void Main(string[] args)
{
string[] annotates = { "1", "2"};
Guides[] g1 = new Guides[2];
g1[0].comments = (string[])annotates.Clone();
g1[1].comments = (string[])annotates.Clone();
Guides[] g2 = new Guides[2];
g2[0].comments = (string[])annotates.Clone();
g2[1].comments = (string[])annotates.Clone();//to be commented later
arrayStruct arrStr1 = new arrayStruct();
arrStr1.guides_array = g1;
arrayStruct arrStr2 = new arrayStruct();
arrStr2.guides_array = g2;
using (MoveSaver objSaver = new MoveSaver(@"C:\1.bin"))
{
MoveAndTime mv1 = new MoveAndTime();
MoveAndTime mv2 = new MoveAndTime();
mv1.MoveStruc = "1";
mv1.timeHLd = DateTime.Now;
mv1.arr = arrStr1;
objSaver.SaveToFile(mv1);
mv2.MoveStruc = "2";
mv2.timeHLd = DateTime.Now;
mv2.arr = arrStr2;
objSaver.SaveToFile(mv2);
}
using (MoveSaver svrObj = new MoveSaver())
{
List<MoveAndTime> MVTobjs = svrObj.DeSerializeObject(@"C:\1.bin");
foreach (MoveAndTime item in MVTobjs)
{
Console.WriteLine(item.arr.guides_array[0].comments[0]);
}
}
}
}
public class MoveSaver : IDisposable
{
public void Dispose()
{
if (fs != null)
{
fs.Close();
}
}
FileStream fs;
StreamWriter sw;
public string filename { get; set; }
public MoveSaver(string FileName)
{
this.filename = FileName;
fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite);
}
public MoveSaver()
{
}
~MoveSaver()
{
if (fs != null)
{
fs.Close();
}
}
public List<MoveAndTime> DeSerializeObject(string filename)
{
List<MoveAndTime> retList = new List<MoveAndTime>();
MoveAndTime objectToSerialize;
Stream stream = File.Open(filename, FileMode.Open);
BinaryFormatter bFormatter = new BinaryFormatter();
while (stream.Position != stream.Length)
{
objectToSerialize = (MoveAndTime)bFormatter.Deserialize(stream);
retList.Add(objectToSerialize);
}
stream.Close();
return retList;
}
public bool SaveToFile(MoveAndTime moveTime)
{
try
{
BinaryFormatter bformatter = new BinaryFormatter();
bformatter.Serialize(fs, moveTime);
return true;
}
catch (Exception)
{
return false;
}
}
}
[Serializable]
public struct MoveAndTime
{
public string MoveStruc;
public DateTime timeHLd;
public arrayStruct arr;
}
[Serializable]
public struct arrayStruct
{
public Guides[] guides_array;
}
[Serializable]
public struct Guides
{
public string[] comments;
public string name;
}
}
In this code an struct contains multiple structures, one of which contains an array. Try the code and it compiles fine, but in the real scenario the whole array is not filled, so there would be other array elements unspecified. To see this effect (in action!) comment the line g2[1].comments = (string[])annotates.Clone();
and try the code now. You will face an error while deserializing. How can I avoid it? Should I define the structure containing the array as a class and new them all (hopefully I am looking for a solution of structure-based kind)?
Edit: I Changed the structs to class and by newing every instance it works fine. Here is the code:
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Collections.Generic;
namespace Serialization
{
class Program
{
static void Main(string[] args)
{
string[] annotates = { "1", "2"};
GuidesClass[] g1 = new GuidesClass[2];
g1[0] = new GuidesClass();
g1[0].comments = (string[])annotates.Clone();
g1[1] = new GuidesClass();
g1[1].comments = (string[])annotates.Clone();
GuidesClass[] g2 = new GuidesClass[2];
g2[0] = new GuidesClass();
g2[0].comments = (string[])annotates.Clone();
g2[1] = new GuidesClass();
//g2[1].comments = (string[])annotates.Clone();
array_cls arrStr1 = new array_cls();
arrStr1.guides_array = g1;
array_cls arrStr2 = new array_cls();
arrStr2.guides_array = g2;
using (MoveSaver objSaver = new MoveSaver(@"C:\1.bin"))
{
M_T mv1 = new M_T();
M_T mv2 = new M_T();
mv1.MoveStruc = "1";
mv1.timeHLd = DateTime.Now;
mv1.arr = arrStr1;
objSaver.SaveToFile(mv1);
mv2.MoveStruc = "2";
mv2.timeHLd = DateTime.Now;
mv2.arr = arrStr2;
objSaver.SaveToFile(mv2);
}
using (MoveSaver svrObj = new MoveSaver())
{
List<M_T> MVTobjs = svrObj.DeSerializeObject(@"C:\1.bin");
foreach (M_T item in MVTobjs)
{
Console.WriteLine(item.arr.guides_array[0].comments[0]);
}
}
}
}
public clas开发者_运维百科s MoveSaver : IDisposable
{
public void Dispose()
{
if (fs != null)
{
fs.Close();
}
}
FileStream fs;
StreamWriter sw;
public string filename { get; set; }
public MoveSaver(string FileName)
{
this.filename = FileName;
fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite);
}
public MoveSaver()
{
}
~MoveSaver()
{
if (fs != null)
{
fs.Close();
}
}
public List<M_T> DeSerializeObject(string filename)
{
List<M_T> retList = new List<M_T>();
M_T objectToSerialize;
Stream stream = File.Open(filename, FileMode.Open);
BinaryFormatter bFormatter = new BinaryFormatter();
while (stream.Position != stream.Length)
{
objectToSerialize = (M_T)bFormatter.Deserialize(stream);
retList.Add(objectToSerialize);
}
stream.Close();
return retList;
}
public bool SaveToFile(M_T moveTime)
{
try
{
BinaryFormatter bformatter = new BinaryFormatter();
bformatter.Serialize(fs, moveTime);
return true;
}
catch (Exception)
{
return false;
}
}
}
[Serializable]
public class M_T
{
public string MoveStruc;
public DateTime timeHLd;
public array_cls arr;
}
[Serializable]
public class array_cls
{
public GuidesClass[] guides_array = new GuidesClass[10];
}
[Serializable]
public class GuidesClass
{
public string[] comments;
public string name;
}
}
I've had a play with the code, and I can repro for both struct and class; ultimately I suspect the issue here is that BinaryFormatter
isn't designed to be appendable quite like that, i.e. I suspect it is misinterpreting data from the next object as part of the current.
FWIW, those really shouldn't be structs, and you are (IMO) horribly over-engineering the save/load code. I changed it so that the save method took a List<MoveAndTime>
, and the load code returned a single List<MoveAndTime>
(i.e. so there is only one outermost object in the stream) and it worked fine, supporting my theory.
If you need to be able to gradually append individual objects, I would suggest protobuf-net; you can use SerializeWithLengthPrefix
to write an object in a way suitable for append, and DeserializeWithLengthPrefix
to read a single object from the stream, or DeserializeItems
to read (as a sequence) all the items in the stream.
For example, using protobuf-net v2 (only available as code at the moment):
using System;
using System.Collections.Generic;
using System.IO;
using ProtoBuf;
namespace Serialization
{
class Program
{
static void Main(string[] args)
{
string[] annotates = { "1", "2" };
Guides[] g1 = new Guides[2];
g1[0].comments = (string[])annotates.Clone();
g1[1].comments = (string[])annotates.Clone();
Guides[] g2 = new Guides[2];
g2[0].comments = (string[])annotates.Clone();
g2[1].comments = (string[])annotates.Clone();//to be commented later
arrayStruct arrStr1 = new arrayStruct();
arrStr1.guides_array = g1;
arrayStruct arrStr2 = new arrayStruct();
arrStr2.guides_array = g2;
using (Stream file = File.Create(@"1.bin"))
{
MoveAndTime mv1 = new MoveAndTime();
MoveAndTime mv2 = new MoveAndTime();
mv1.MoveStruc = "1";
mv1.timeHLd = DateTime.Now;
mv1.arr = arrStr1;
Serializer.SerializeWithLengthPrefix(file, mv1, PrefixStyle.Base128, Serializer.ListItemTag);
mv2.MoveStruc = "2";
mv2.timeHLd = DateTime.Now;
mv2.arr = arrStr2;
Serializer.SerializeWithLengthPrefix(file, mv2, PrefixStyle.Base128, Serializer.ListItemTag);
}
using (Stream file = File.OpenRead(@"1.bin"))
{
List<MoveAndTime> MVTobjs = Serializer.Deserialize<List<MoveAndTime>>(file);
foreach (MoveAndTime item in MVTobjs)
{
Console.WriteLine(item.arr.guides_array[0].comments[0]);
}
}
}
}
[ProtoContract]
public struct MoveAndTime
{
[ProtoMember(1)]
public string MoveStruc;
[ProtoMember(2)]
public DateTime timeHLd;
[ProtoMember(3)]
public arrayStruct arr;
}
[ProtoContract]
public struct arrayStruct
{
[ProtoMember(1)]
public Guides[] guides_array;
}
[ProtoContract]
public struct Guides
{
[ProtoMember(1)]
public string[] comments;
[ProtoMember(2)]
public string name;
}
}
v1 (available as a dll; more stable) would work almost the same, but doesn't support structs - only classes.
But to emphasise:
- they should be classes
- public fields are a bad idea
精彩评论