Last active
May 20, 2017 00:51
-
-
Save JKamsker/e06deb09aaf2da2e8828d6fcee4c8472 to your computer and use it in GitHub Desktop.
A simple Dictionary<T,TX> serializer
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 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