admin管理员组文章数量:1356441
I'm starting with serialization in C# and am a bit confused about how ISerializable works. I have something like
[Serializable]
public class MyClass : ISerializable
{
public enum MyEnum {FIRST, SECOND}
private Dictionary<MyEnum,Vector2[]> dict;
...
}
I want to manually/low-level (de)serialize it, i.e. with GetObjectData
and the constructor. Now in Java I would just write down something like
os.write(dict.size());
for (var pair : dict) {
os.write(pair.getKey().ordinal());
os.write(pair.getValue().length());
for (Vector2 vec : pair.getValue()) {
os.write(vec.getX());
os.write(vec.getY());
}
}
Since I know the order in which the stuff was written, I can deserialize it as well.
However, in C# the SerializationInfo info
has a method AddValue
which wants a string key from me. Vector2 isn't implementing ISerializable. I thought of writing a utility method, but then again I need the keys to be different every time? Can't just use info.AddValue("x", v.X)
because there might be more vectors. Can't use info.AddValue("x_"+i, v[i].X)
because there might be multiple arrays along the line.
What to do? I'm missing something like a compound element.
I'm starting with serialization in C# and am a bit confused about how ISerializable works. I have something like
[Serializable]
public class MyClass : ISerializable
{
public enum MyEnum {FIRST, SECOND}
private Dictionary<MyEnum,Vector2[]> dict;
...
}
I want to manually/low-level (de)serialize it, i.e. with GetObjectData
and the constructor. Now in Java I would just write down something like
os.write(dict.size());
for (var pair : dict) {
os.write(pair.getKey().ordinal());
os.write(pair.getValue().length());
for (Vector2 vec : pair.getValue()) {
os.write(vec.getX());
os.write(vec.getY());
}
}
Since I know the order in which the stuff was written, I can deserialize it as well.
However, in C# the SerializationInfo info
has a method AddValue
which wants a string key from me. Vector2 isn't implementing ISerializable. I thought of writing a utility method, but then again I need the keys to be different every time? Can't just use info.AddValue("x", v.X)
because there might be more vectors. Can't use info.AddValue("x_"+i, v[i].X)
because there might be multiple arrays along the line.
What to do? I'm missing something like a compound element.
Share Improve this question edited Mar 28 at 12:51 Uwe Keim 40.8k61 gold badges190 silver badges304 bronze badges asked Mar 27 at 23:21 SK19SK19 1831 silver badge8 bronze badges 12 | Show 7 more comments5 Answers
Reset to default 1If you just wanted to convert an array of vectors to bytes you could possibly just cast it directly using MemoryMarshal.Cast<Vector2, byte>
. This might not be safe for storage since .Net could change how vector2 is represented in memory, especially considering big/little endianess.
Since you have a more complex object graph my recommendation would be to use a serialization library. My preference is protobuf , but there are several others. Since Protobuf cannot handle Vector2 by default we need to do some configuring:
using ProtoBuf.Meta;
...
var typeModel = RuntimeTypeModel.Create();
typeModel.Add(typeof(Vector2), false).Add(nameof(Vector2.X), nameof(Vector2.Y));
var data = new Dictionary<MyEnum, Vector2[]>(){
{ MyEnum.FIRST, [Vector2.UnitX, Vector2.UnitY] },
{ MyEnum.SECOND, [Vector2.One,] } };
var ms = new MemoryStream();
typeModel.Serialize(ms, data);
ms.Position = 0;
var result = typeModel.Deserialize<Dictionary<MyEnum, Vector2[]>>(ms);
You may want to consider some kind of interface to make it easier to switch between serialization methods.
An alternative would be to use BinaryReader/Writer directly. A common way to deal with sequences of something is to prefix the length, i.e. length | v[0].X | v[0].Y | v[1].X...
. But in my experience serializing manually require significantly more code, and tend to be more fragile if you ever want to add more data in the future.
Whatever you do you need to follow a protocol for how objects and primitives are converted to bytes. BinaryFormatter was obsoleted for several very good reasons. There are well known third party protocol like protobuf, or you can invent something yourself. But chances are that the third party protocols will be better designed and easier to use than anything you would come up with yourself.
If you want this to be highly performant and space-efficient, you could consider using MemoryPack (for which you would need to install the MemoryPack
NuGet packager).
For your example it would look like this:
using System.Numerics;
using System.Text;
using MemoryPack;
namespace ConsoleApp1;
public static class Program
{
static void Main()
{
var unserialised = new MyClass();
unserialised.Add(MyClass.MyEnum.FIRST, [new Vector2(1, 2), new Vector2(3, 4), new Vector2( 5, 6)]);
unserialised.Add(MyClass.MyEnum.SECOND, [new Vector2(7, 8), new Vector2(9, 10), new Vector2(11, 12)]);
Console.WriteLine(unserialised);
byte[] data = MemoryPackSerializer.Serialize(unserialised);
Console.WriteLine("Data length (bytes) = " + data.Length + "\n");
var deserialised = MemoryPackSerializer.Deserialize<MyClass>(data);
Console.WriteLine(deserialised);
}
}
[MemoryPackable]
public partial class MyClass
{
public enum MyEnum { FIRST, SECOND }
[MemoryPackInclude]
private Dictionary<MyEnum, Vector2[]> dict = new();
public void Add(MyEnum key, Vector2[] value)
{
dict.Add(key, value);
}
public override string ToString()
{
var result = new StringBuilder();
foreach (var (key, value) in dict)
{
result.Append(key + ": ");
result.AppendLine(string.Join(", ", value));
}
return result.ToString();
}
}
This serialises the data into 69 bytes, which is an overhead of 21 bytes over the space needed for 12 raw floats. This is a fixed overhead for the Dictionary
- if you increase the size of the vectors, the serialised data will only increase by the binary size of the additional floats (4 bytes each).
MemoryPack
is an evolution of the older (and slightly less performant) MessagePack.
TL;DR:
- If you are not interested in alternative ways and want to use only
BinaryFormatter
, see the solution above the last code block. - If you don't mind using a 3rd party library and the main goal is a compact payload, then just see the first example below. You can use the same approach as with
BinaryFormatter
, still, the result result is much more compact.
If you want to use binary serialization that relies on the IFormatter
infrastructure ([Serializable]
attribute, ISerializable
, etc.) in a safe way, feel free to try this serializer from this library (disclaimer: I'm the author).
I want to manually/low-level (de)serialize it, i.e. with
GetObjectData
and the constructor.
It actually supports the Vector2
type natively (depending on the targeted framework), so you don't even need custom serialization:
public enum MyEnum {FIRST, SECOND}
[Serializable]
public class MyClass // : ISerializable - not needed (but read further)
{
private Dictionary<MyEnum,Vector2[]> dict;
public MyClass Add(MyEnum key, params Vector2[] values)
{
dict[key] = values;
return this;
}
}
And then the usage:
var myClass = new MyClass()
.Add(MyEnum.FIRST, new (1, 1), new (2, 2))
.Add(MyEnum.SECOND, new (3, 3), new (4, 4));
using var stream = new MemoryStream();
// serialization
var serializer = new BinarySerializationFormatter();
serializer.SerializeToStream(stream, myClass);
// deserialization: in safe mode you must enlist the expected custom types
// Note that only MyEnum should be specified because both Dictionary<,> and Vector2 are natively supported
stream.Position = 0;
var clone = serializer.DeserializeFromStream<MyClass>(stream,
expectedCustomTypes: typeof(MyEnum));
If you still want customization (e.g. to prevent serializing the assembly identity of MyEnum
), you can implement the ISerializable
interface after all. You can do it like this:
[Serializable]
public class MyClassCustom : MyClass, ISerializable
{
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
// a small customization: we use ints instead of enums so safe
// deserialization does not need to specify the enum as an expected type:
info.AddValue("keys", dict.Keys.Select(k => (int)k).ToArray()); // int[]
// Note that this would NOT work by BinaryFormatter because Vector2 is
// not marked as [Serializable]. But see my notes below the example.
info.AddValue("values", dict.Values.ToArray()); // Vector2[][]
}
private MyClassCustom(SerializationInfo info, StreamingContext context)
{
var keys = (int[])info.GetValue("keys", typeof(int[]));
var values = (Vector2[][])info.GetValue("values", typeof(Vector2[][]));
for (int i = 0; i < keys.Length; i++)
Add((MyEnum)keys[i], values[i]);
}
public MyClassCustom() { }
}
However, in C# the
SerializationInfo
info has a methodAddValue
which wants a string key from me.Vector2
isn't implementingISerializable
. I thought of writing a utility method, but then again I need the keys to be different every time?
Note that the code above is happy with a single key both for the keys and values. But it's just because we use a different formatter that happens to support Vector2
natively. If you have to use the legacy BinaryFormatter
for some reason, then you can replace the Vector2
instances by ValueTuple<float, float>
for example (similarly to the MyEnum
-> int
conversion), as tuples are still serializable.
Anyway, the serialization now is very similar. The only practical difference is that the serialization stream does not contain MyEnum
now so it's not needed to be specified:
var myClass = new MyClassCustom()
.Add(MyEnum.FIRST, new (1, 1), new (2, 2))
.Add(MyEnum.SECOND, new (3, 3), new (4, 4));
using var stream = new MemoryStream();
// serialization
var serializer = new BinarySerializationFormatter();
serializer.SerializeToStream(stream, myClass);
// deserialization: note that MyEnum is not needed to be specified now
stream.Position = 0;
var clone = serializer.DeserializeFromStream<MyClassCustom>(stream);
Further notes:
- I created an online demo with your case.
- To avoid the possible security issues with the classic
IFormatter
serialization infrastructure make sure you read the security notes at the Remarks section of theBinarySerializationFormatter
class. To be completely robust, not only itsSafeMode
should be used (enabled by default), but if you choose to use custom serialization, then make sure you add some validation in the deserialization constructor as well. See the 2nd example at the linked page for more details. - You can use the
ExtractExpectedTypes
method for the default case. May not be enough in complex cases, or when the extracted types are interfaces or non-sealed classes. - To achieve an even more compact result, you can implement the
IBinarySerializable
interface instead. In fact, if you follow the pattern in the documentation and you can cover the full object graph with it, then you don't even need my serializer.
Part of the point of Vector2
is that it is "raw", i.e. it is a direct representation of the data. That means that you can simply access the underlying bytes directly, if you don't need to worry about things like endianness. The following gets the same data as a ReadOnlySpan<byte>
without any arrays or conversions - the span literally points at the original variable (and the contents will change if you change the values in a
):
Vector2 a = new(42.5f, 16.4f);
ReadOnlySpan<byte> bytesDirect = MemoryMarshal.Cast<Vector2, byte>(
MemoryMarshal.CreateReadOnlySpan(ref a, 1));
You should find that bytesDirect
has 8 bytes with predictable values. This is like various pointer-based unsafe
approaches, but: without the unsafe
in your code - i.e. you can think of ReadOnlySpan<byte>
as a byte* ptr
and int length
pair.
A Vector2[]
(from the question) would be the same, except simpler because a Vector2[]
can already be treated as a Span<Vector2>
:
Vector2[] values = ...
ReadOnlySpan<byte> bytesDirect = MemoryMarshal.Cast<Vector2, byte>(values);
This creates a span over the entire array body, i.e. the length will be 8*values.Length
.
As of .NET 9, there is no builtin binary serialization technology in .NET.
You can, however, use BinaryReader
and BinaryWriter
and manually serialize similar as you have demonstrated with Java.
本文标签: cHow do I byteserialize Vector2Stack Overflow
版权声明:本文标题:c# - How do I byte-serialize Vector2[]? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744063562a2584572.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
ISerializable
. See also SYSLIB0051: Legacy serialization support APIs are obsolete - you'll get a compiler warning if you implement aGetObjectData()
method as of .NET 8. – dbc Commented Mar 27 at 23:46