Last active
January 5, 2018 16:15
-
-
Save micdenny/b67e38a0455c22a8429cb2890a5f6d31 to your computer and use it in GitHub Desktop.
BinaryData: how to manage an optional property using ReadObject
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Reflection; | |
using Newtonsoft.Json; | |
using Syroot.BinaryData; | |
using Syroot.BinaryData.Extensions; | |
namespace ConsoleApp1 | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
var message = new Message | |
{ | |
Header = new Header | |
{ | |
Version = 2, | |
BodyLength = 2 | |
}, | |
Body = new MessageBody | |
{ | |
Data = 123 | |
}, | |
Optionals = new OptionalList | |
{ | |
new OptionalTwo | |
{ | |
Size = 555 | |
} | |
} | |
}; | |
Console.WriteLine(JsonConvert.SerializeObject(message, Formatting.Indented)); | |
byte[] bytes; | |
using (var stream = new MemoryStream()) | |
{ | |
stream.WriteObject(message, ByteConverter.BigEndian); | |
bytes = stream.ToArray(); | |
} | |
Console.WriteLine(); | |
Console.WriteLine(); | |
Console.WriteLine(JsonConvert.SerializeObject(bytes, Formatting.Indented)); | |
Console.WriteLine(); | |
for (int i = 0; i < bytes.Length; i++) | |
{ | |
Console.WriteLine($"[{i}]={bytes[i]}"); | |
} | |
Message deserializedMessage; | |
using (var stream = new MemoryStream(bytes)) | |
{ | |
deserializedMessage = stream.ReadObject<Message>(ByteConverter.BigEndian); | |
} | |
Console.WriteLine(); | |
Console.WriteLine(); | |
Console.WriteLine(JsonConvert.SerializeObject(deserializedMessage, Formatting.Indented)); | |
} | |
} | |
public static class OptionalStreamExtensions | |
{ | |
private static Dictionary<OptionalId, MethodInfo> _readObjectMethods; | |
static OptionalStreamExtensions() | |
{ | |
_readObjectMethods = new Dictionary<OptionalId, MethodInfo>(); | |
var readObjectMethod = typeof(StreamExtensions).GetMethod("ReadObject", new[] { typeof(Stream), typeof(ByteConverter) }); | |
var assembly = Assembly.GetAssembly(typeof(IOptional)); | |
foreach (var type in assembly.GetTypes()) | |
{ | |
var optionalIdAttribute = type.GetCustomAttribute<OptionalIdAttribute>(); | |
if (optionalIdAttribute != null) | |
{ | |
_readObjectMethods.Add(optionalIdAttribute.Id, readObjectMethod.MakeGenericMethod(type)); | |
} | |
} | |
} | |
public static object ReadObject(this Stream stream, OptionalId id, ByteConverter converter = null) | |
{ | |
if (_readObjectMethods.ContainsKey(id)) | |
{ | |
var method = _readObjectMethods[id]; | |
return method.Invoke(null, new object[] { stream, converter }); | |
} | |
throw new Exception($"Can't find the Optional type for optional id = {id} ({(ushort)id}). Please add the OptionalIdAttribute on the Optional{id} class."); | |
} | |
} | |
public class OptionalsConverter : IBinaryConverter | |
{ | |
public object Read(Stream stream, object instance, BinaryMemberAttribute memberAttribute, ByteConverter converter) | |
{ | |
if (stream.IsEndOfStream()) | |
{ | |
return null; | |
} | |
OptionalId id; | |
using (stream.TemporarySeek()) | |
{ | |
id = stream.ReadEnum<OptionalId>(converter: converter); | |
} | |
return stream.ReadObject(id, converter); | |
} | |
public void Write(Stream stream, object instance, BinaryMemberAttribute memberAttribute, object value, ByteConverter converter) | |
{ | |
if (value != null) | |
{ | |
stream.WriteObject(value, converter); | |
} | |
} | |
} | |
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] | |
sealed class OptionalIdAttribute : Attribute | |
{ | |
readonly OptionalId _id; | |
public OptionalIdAttribute(OptionalId id) | |
{ | |
_id = id; | |
} | |
public OptionalId Id | |
{ | |
get { return _id; } | |
} | |
} | |
[BinaryObject(Explicit = true)] | |
public abstract class AbstractMessage<T> | |
{ | |
[BinaryMember(Order = 1)] | |
public Header Header { get; set; } | |
[BinaryMember(Order = 2)] | |
public T Body { get; set; } | |
public IOptionalList Optionals | |
{ | |
get | |
{ | |
var optionals = new OptionalList(); | |
var query = this.GetType().GetProperties().Where(x => typeof(IOptional).IsAssignableFrom(x.PropertyType)).OrderBy(x => x.Name); | |
foreach (var property in query) | |
{ | |
var opt = (IOptional)property.GetValue(this); | |
if (opt != null) | |
{ | |
optionals.Add(opt); | |
} | |
} | |
return optionals; | |
} | |
set | |
{ | |
foreach (var opt in this.GetType().GetProperties().Where(x => typeof(IOptional).IsAssignableFrom(x.PropertyType))) | |
{ | |
opt.SetValue(this, null); | |
} | |
foreach (var opt in value.Where(x => x != null)) | |
{ | |
var property = this.GetType().GetProperties().FirstOrDefault(x => opt.GetType().IsAssignableFrom(x.PropertyType)); | |
if (property != null) | |
{ | |
property.SetValue(this, opt); | |
} | |
} | |
} | |
} | |
// HACK: needed because BinaryData does not yet support ReadObject every IEnumerable | |
[BinaryMember(Order = 3, Length = 3, Converter = typeof(OptionalsConverter))] | |
private IOptional[] InternalOptionals | |
{ | |
get | |
{ | |
return this.Optionals.ToArray(); | |
} | |
set | |
{ | |
this.Optionals = new OptionalList(value); | |
} | |
} | |
} | |
public interface IOptionalList : IReadOnlyList<IOptional> | |
{ | |
} | |
public class OptionalList : List<IOptional>, IOptionalList | |
{ | |
public OptionalList() | |
{ | |
} | |
public OptionalList(IEnumerable<IOptional> collection) : base(collection) | |
{ | |
} | |
} | |
public class Header | |
{ | |
public ushort Version { get; set; } | |
public ushort BodyLength { get; set; } | |
} | |
[BinaryObject(Explicit = true, Inherit = true)] | |
public class Message : AbstractMessage<MessageBody> | |
{ | |
public OptionalOne OptionalOne { get; set; } | |
public OptionalTwo OptionalTwo { get; set; } | |
public OptionalThree OptionalThree { get; set; } | |
} | |
public class MessageBody | |
{ | |
[BinaryMember(Order = 1)] | |
public ushort Data { get; set; } | |
} | |
public enum OptionalId : ushort | |
{ | |
One = 1, | |
Two = 2, | |
Three = 3 | |
} | |
public interface IOptional | |
{ | |
OptionalId Id { get; set; } | |
byte Len { get; set; } | |
} | |
[OptionalId(OptionalId.One)] | |
[BinaryObject(Explicit = true)] | |
public class OptionalOne : IOptional | |
{ | |
[BinaryMember(Order = 1)] | |
public OptionalId Id { get; set; } = OptionalId.One; | |
[BinaryMember(Order = 2)] | |
public byte Len { get; set; } = 4; | |
[BinaryMember(Order = 3)] | |
public ushort Start { get; set; } | |
[BinaryMember(Order = 4)] | |
public ushort Stop { get; set; } | |
} | |
[OptionalId(OptionalId.Two)] | |
[BinaryObject(Explicit = true)] | |
public class OptionalTwo : IOptional | |
{ | |
[BinaryMember(Order = 1)] | |
public OptionalId Id { get; set; } = OptionalId.Two; | |
[BinaryMember(Order = 2)] | |
public byte Len { get; set; } = 8; | |
[BinaryMember(Order = 3)] | |
public ulong Size { get; set; } | |
} | |
[OptionalId(OptionalId.Three)] | |
[BinaryObject(Explicit = true)] | |
public class OptionalThree : IOptional | |
{ | |
[BinaryMember(Order = 1)] | |
public OptionalId Id { get; set; } = OptionalId.Three; | |
[BinaryMember(Order = 2)] | |
public byte Len { get; set; } = 3; | |
[BinaryMember(Order = 3)] | |
public byte X { get; set; } | |
[BinaryMember(Order = 4)] | |
public byte Y { get; set; } | |
[BinaryMember(Order = 5)] | |
public byte Z { get; set; } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment