Skip to content

Instantly share code, notes, and snippets.

@JKamsker
Last active May 20, 2017 00:51
Show Gist options
  • Save JKamsker/e06deb09aaf2da2e8828d6fcee4c8472 to your computer and use it in GitHub Desktop.
Save JKamsker/e06deb09aaf2da2e8828d6fcee4c8472 to your computer and use it in GitHub Desktop.
A simple Dictionary<T,TX> serializer
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
public static class Usage
{
public static void Use()
{
//Let's create a dummy dictionary with some entities
Dictionary<string, string> myDictionary = new Dictionary<string, string>();
myDictionary["HalloWelt"] = "lola";
myDictionary["HalloWelt1"] = "lola2";
//Serialize the dictionary to a byte array
var serialized = myDictionary.Serialize();
//Deserialize that byte array to a dictionary<string,string>
var deseri = serialized.DeSerialize<string, string>();
Console.WriteLine(deseri["HalloWelt"]);
//Same, but not as pretty as the previous example
serialized = DictionarySerializer<string, string>.Serialize(myDictionary);
deseri = DictionarySerializer<string, string>.Deserialize(serialized);
var myDictionary1 = new Dictionary<string, Dictionary<string, string>>
{
["HalloWelt"] = new Dictionary<string, string>
{
["a"] = "b",
["c"] = "ba",
["d"] = "bs",
["e"] = "bfa",
},
["HalloWelt1"] = new Dictionary<string, string>
{
["xa"] = "xb",
["xc"] = "xba",
["xd"] = "xbs",
["xe"] = "xbfa",
},
};
serialized = myDictionary1.Serialize();
var deserialized = serialized.DeSerialize<string, Dictionary<string, Dictionary<string, int>>>();
Console.ReadLine();
}
}
public static class TBinaryExtendor
{
/// <summary>
/// Serializes a Dictionary<T,TX> to a handy byte array
/// </summary>
/// <typeparam name="T">Dictionary Key</typeparam>
/// <typeparam name="TX">Dictionary Value</typeparam>
/// <param name="input">Input dictionary</param>
/// <returns></returns>
public static byte[] Serialize<T, TX>(this Dictionary<T, TX> input)
{
return DictionarySerializer<T, TX>.Serialize(input);
}
/// <summary>
/// Deserializes the given byte array to a dictionary of your choice
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TX"></typeparam>
/// <param name="input"></param>
/// <returns></returns>
public static Dictionary<T, TX> DeSerialize<T, TX>(this byte[] input)
{
return DictionarySerializer<T, TX>.Deserialize(input);
}
public static Dictionary<T, TX> DeSerialize<T, TX>(this BinaryReader input)
{
return DictionarySerializer<T, TX>.Deserialize(input);
}
}
/// <summary>
/// A serialized Dictionary conists of (for example, string):
/// (int32)Amount of entities
/// Entity:
/// (int32)keylength (amount of bytes after theese 4 bytes)
/// (string)key[keylength]
/// (int32)valuelength
/// (string)key[valuelength]
///
/// Please note that this serializer is not able to de/serialize non-primitive types by default,
/// if you want to do so, please have a look at TBinaryAccessor.RegisterExtension
/// </summary>
/// <typeparam name="T">Dictionary Key</typeparam>
/// <typeparam name="TX">Dictionary Value</typeparam>
public static class DictionarySerializer<T, TX>
{
public static byte[] Serialize(Dictionary<T, TX> input)
{
//Let's create a Memorystream and write our bytes conviniently with a BinaryWriter onto it
using (var msa = new MemoryStream())
using (BinaryWriter bw = new BinaryWriter(msa))
{
//Write the amount of KeyValuePairs onto the stream
bw.Write(input.Count);
foreach (KeyValuePair<T, TX> val in input)
{
Serialize(val, bw);
}
return msa.ToArray();
}
}
/// <summary>
/// Writes the Key+Value onto the stream and let the TBinaryAccessor handle how it's done.
/// </summary>
/// <param name="input"></param>
/// <param name="bw"></param>
public static void Serialize(KeyValuePair<T, TX> input, BinaryWriter bw)
{
//Write the key onto the stream
TBinaryAccessor<T>.Write(bw, input.Key);
//Write the key onto the stream
TBinaryAccessor<TX>.Write(bw, input.Value);
}
/// <summary>
/// Basically the same as Serialize(Dictionary<T, TX> input) but now the other way round
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static Dictionary<T, TX> Deserialize(byte[] input)
{
using (MemoryStream ms = new MemoryStream(input))
using (BinaryReader br = new BinaryReader(ms))
{
return Deserialize(br);
}
}
public static Dictionary<T, TX> Deserialize(BinaryReader br)
{
//Read the amount of KeyValuePairs from the "header"
var dictSize = br.ReadInt32();
//Create a new instance of the Dictionary requested, use the constructor which accepts the dictSize to "boost" the performance a little
var dstDict = new Dictionary<T, TX>(dictSize);
for (int i = 0; i < dictSize; i++)
{
//Read the first value, set it as key and then read the second one and set it as value
//As above, let the TBinaryAccessor handle how it's done.
dstDict[TBinaryAccessor<T>.Read(br)] = TBinaryAccessor<TX>.Read(br);
}
return dstDict;
}
}
/// <summary>
/// Handles the type de/serialization
/// </summary>
/// <typeparam name="T"></typeparam>
public static class TBinaryAccessor<T>
{
//For each access we need a delegate which handles the de/serialization by accepting
//in both cases a stream and reads/writes on/from it
//This delegate reads the given type from the stream
private static Func<BinaryReader, T> _storReadFunc;
//--"-- writes the given data from the given instance of type T onto the stream
private static Action<BinaryWriter, T> _storWriteAct;
/// <summary>
/// As this serializer isn't able to de/serialize non-primitive types on it's own by default, you could just
/// Extend its functionality by registering a custom Type
/// </summary>
/// <param name="readFunc">null won't change the delegate</param>
/// <param name="writeAct">null won't change the delegate</param>
public static void RegisterExtension(Func<BinaryReader, T> readFunc = null, Action<BinaryWriter, T> writeAct = null)
{
_storReadFunc = readFunc ?? _storReadFunc;
_storWriteAct = writeAct ?? _storWriteAct;
}
/// <summary>
/// If you want to use a different Encoding for string en/decoding, you can do that here
/// </summary>
public static Encoding BaseEncoding = Encoding.UTF8;
/// <summary>
/// Static initializer;
/// Basically searches for a method in the BinaryWriter/Reader class to automatically handle the given types
/// </summary>
static TBinaryAccessor()
{
var typeOfT = typeof(T);
if (typeOfT == typeof(string))
{
//Looks a bit ugly, but ¯\_(ツ)_/¯
//For reading:
// 1.Read the len of the next string
// 2.Read the amount of len of bytes and decode it to a string
//For writing:
// 1: Convert the given string to a byte[]
// 2: Write the len of bytes in the array onto the stream
// 3: Write the bytes onto the stream
RegisterExtension(
new Func<BinaryReader, string>(x => BaseEncoding.GetString(x.ReadBytes(x.ReadInt32()))) as Func<BinaryReader, T>,
new Action<BinaryWriter, string>((m, x) =>
{
var dstBytes = BaseEncoding.GetBytes(x);
m.Write(dstBytes.Length);
m.Write(dstBytes);
}) as Action<BinaryWriter, T>
);
}
else if (typeOfT.IsPrimitive)
{
//Search for a method to read the given type
var readMethodInfo = TypeSearcher.GetMethodsFromType(typeof(BinaryReader), new Type[0], typeOfT)
.FirstOrDefault(m => m.Name.StartsWith("Read") && m.Name != "Read");
if (readMethodInfo != null)
{
//Create a delegate, so that we don't have to use Methodinfo.Invoke which slows down the whole process
_storReadFunc = (Func<BinaryReader, T>)readMethodInfo.CreateDelegate(typeof(Func<BinaryReader, T>));
}
//Search for a method to write the given type onto a stream
var writeMethodInfo = TypeSearcher.GetMethodsFromType(typeof(BinaryWriter), new Type[] { typeOfT }).FirstOrDefault();
if (writeMethodInfo != null)
{
_storWriteAct = (Action<BinaryWriter, T>)writeMethodInfo.CreateDelegate(typeof(Action<BinaryWriter, T>));
}
}
else
{
_storWriteAct = new Action<BinaryWriter, T>((bw, data) =>
{
var serialized = MatroschkaResolve.GetWriteMethodInfo(data).Invoke(null, new object[] { data }) as byte[];
if (serialized != null)
bw.Write(serialized);
});
_storReadFunc = new Func<BinaryReader, T>(br =>
{
var eval = MatroschkaResolve.GetReadMethodInfo<T>();
return (T)eval?.Invoke(null, new object[] { br });
});
}
}
/// <summary>
/// Use the previosly resolved method to read the given type
/// </summary>
/// <param name="binaryReader"></param>
/// <returns></returns>
public static T Read(BinaryReader binaryReader)
{
if (_storReadFunc != null)
{
return _storReadFunc(binaryReader);
}
return default(T);
}
/// <summary>
/// Use the previosly resolved method to write the given object onto the stream
/// </summary>
/// <param name="bw"></param>
/// <param name="data"></param>
/// <returns></returns>
public static bool Write(BinaryWriter bw, T data)
{
if (_storWriteAct != null)
{
_storWriteAct(bw, data);
return true;
}
return false;
}
}
/// <summary>
/// Really Really unsexy thing....
/// More documentation is comming soon :/
/// </summary>
public class MatroschkaResolve
{
public static Dictionary<ulong, MethodInfo> _writeCache = new Dictionary<ulong, MethodInfo>();
public static Dictionary<ulong, MethodInfo> _readCache = new Dictionary<ulong, MethodInfo>();
public static MethodInfo GetWriteMethodInfo<T>(T inputDict)
{
var typeOfT = typeof(T);
Type[] args = null;
if (typeOfT.IsDictionary() && (args = typeOfT.GetGenericArguments()) != null && args.Length == 2)
{
var metadataToken = unchecked(((ulong)(uint)args[0].MetadataToken << 32) | (ulong)(uint)args[1].MetadataToken);
if (_writeCache.TryGetValue(metadataToken, out var dstMethod))
{
return dstMethod;
}
else
{
var methodArray = typeof(TBinaryExtendor).UnderlyingSystemType.GetMethods().Where(m => m.GetParameters().Length == 1).ToArray();
MethodInfo selected = null;
for (var i = 0; i < methodArray.Length; i++)
{
var genArgs = methodArray[i].GetGenericArguments();
foreach (Type t in genArgs)
{
if (genArgs[i].IsGenericParameter || t == args[i])
{
selected = methodArray[i];
}
else
{
selected = null;
break;
}
}
if (selected != null)
{
break;
}
}
dstMethod = selected?.MakeGenericMethod(args);
if (dstMethod != null)
{
_writeCache[metadataToken] = dstMethod;
return dstMethod;
}
}
}
return null;
}
public static MethodInfo GetReadMethodInfo<T>()
{
Type[] args = null;
var typeOfT = typeof(T);
if (typeOfT.IsDictionary() && (args = typeOfT.GetGenericArguments()) != null && args.Length == 2)
{
var metadataToken = unchecked(((ulong)(uint)args[0].MetadataToken << 32) | (ulong)(uint)args[1].MetadataToken);
if (_readCache.TryGetValue(metadataToken, out var dstMethod))
{
return dstMethod;
}
else
{
var method = TypeSearcher.GetMethodsFromType(typeof(TBinaryExtendor), new Type[] { typeof(BinaryReader) }).FirstOrDefault();
dstMethod = method?.MakeGenericMethod(args);
if (dstMethod != null)
{
_readCache[metadataToken] = dstMethod;
return dstMethod;
}
}
}
return null;
}
}
public static class ReflectionExtensions
{
public static bool IsDictionary(this Type input) => (input.IsGenericType && input.GetGenericTypeDefinition() == typeof(Dictionary<,>));
}
internal class TypeSearcher
{
/// <summary>
/// Gets us all methods from a class which match our needs
/// </summary>
/// <param name="classToSearchIn">The parent class where the methods are located</param>
/// <param name="matchInputParams">null means any</param>
/// <param name="outputparam">null means any</param>
/// <returns></returns>
public static IEnumerable<MethodInfo> GetMethodsFromType(Type classToSearchIn, Type[] matchInputParams = null, Type outputparam = null)
{
IEnumerable<MethodInfo> methodArray = classToSearchIn.UnderlyingSystemType.GetMethods();
if (matchInputParams != null)
{
methodArray = methodArray.Where(m => m.GetParameters().Length == matchInputParams.Length);
if (matchInputParams.Length > 0)
{
methodArray = methodArray
.Where(a => a.GetParameters()
.Where((ParameterInfo info, int i) => info.ParameterType == matchInputParams[i])
.Any());
}
}
if (outputparam != null)
methodArray = methodArray.Where(m => m.ReturnParameter?.ParameterType == outputparam);
return methodArray;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment