Skip to content

Instantly share code, notes, and snippets.

@micdenny
Last active January 5, 2018 16:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save micdenny/b67e38a0455c22a8429cb2890a5f6d31 to your computer and use it in GitHub Desktop.
Save micdenny/b67e38a0455c22a8429cb2890a5f6d31 to your computer and use it in GitHub Desktop.
BinaryData: how to manage an optional property using ReadObject
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