Ibasa.Pikala is a .NET library for for pickling object graphs, it's designed to solve the same problems as python cloudpickle but for dotnet.
Pikala can also serialise type definitions and reconstruct them into dynamic assemblies on the other side. This is especially useful for cluster and cloud computing, see Introduction to Pikala.
Pikal is only supported to send objects between the exact same version.
Using Pikala for long-term object storage is not supported and strongly discouraged.
Security notice: one should only load pikala data from trusted sources as otherwise Deserialize can lead to arbitrary code execution resulting in a critical security vulnerability.
You can add this library to your project using NuGet.
Serialize a Tuple<int, string>:
using Ibasa.Pikala
var obj = Tuple.Create(123, "hello world");
var memoryStream = new MemoryStream();
var pickler = new Pickler();
pickler.Serialize(memoryStream, obj);Deserialize a Func<int, int>:
using Ibasa.Pikala
// This is a snapshot of serializing `(Func<int, int>)Math.Abs`
var memoryStream = new MemoryStream(Convert.FromBase64String("UEtMQQAABgACMCsADVN5c3RlbS5GdW5jYDIBAgoKEgAAAQAAAAIDQWJzAQAACgACAAoAASsAC1N5c3RlbS5NYXRoAQAA"));
var pickler = new Pickler();
var function = pickler.Deserialize(memoryStream) as Func<int, int>;
var result = function(-123);
Assert.Equal(123, result);Tell Pikala to serialize type definitions not references for the current assembly:
using Ibasa.Pikala
var pickler = new Pickler(assembly => assembly == System.Reflection.Assembly.GetExecutingAssembly() ? AssemblyPickleMode.PickleByValue : AssemblyPickleMode.Default);Customize how Pikala serializes a type:
public sealed class DictionaryReducer : IReducer
{
readonly Type _dictionary;
public DictionaryReducer()
{
_dictionary = typeof(Dictionary<,>);
}
public Type Type => _dictionary;
public (MethodBase, object, object[]) Reduce(Type type, object obj)
{
var getEnumerator = type.GetMethod("GetEnumerator");
var getCount = type.GetProperty("Count").GetGetMethod();
var enumerator = getEnumerator.Invoke(obj, null);
var count = (int)getCount.Invoke(obj, null);
var genericParameters = enumerator.GetType().GetGenericArguments();
var keyValuePairType = typeof(KeyValuePair<,>).MakeGenericType(genericParameters);
var items = Array.CreateInstance(keyValuePairType, count);
var enumeratorType = enumerator.GetType();
var getCurrent = enumeratorType.GetProperty("Current").GetGetMethod();
var moveNext = enumeratorType.GetMethod("MoveNext");
var index = 0;
while((bool)(moveNext.Invoke(enumerator, null)))
{
var value = getCurrent.Invoke(enumerator, null);
items.SetValue(value, index++);
}
var ctor = type.GetConstructor(new Type[] { typeof(IEnumerable<>).MakeGenericType(keyValuePairType) });
return (ctor, null, new object[] { items });
}
}
var pickler = new Pickler();
pickler.RegisterReducer(new DictionaryReducer());Pikala makes a best effort to serialize most objects.
- Pointers will be explicitly failed (this doesn't apply to
System.IntPtrandUIntPtrwhich are serialized as 64bit integers). - Primitive types (like
intorstringare explicitly handled and written out bySystem.IO.BinaryWriter. - Reflection types like
TypeandFieldInfoare explicitly handled and either written out as named references or as full definitions that can be rebuilt into dynamic modules viaSystem.Reflection.Emit. - The product types
System.ValueTuple,System.Tupleare explictly handled and written out as length, the type of the tuple, and each item. - Arrays are explictly handled:
- Arrays write out their rank, lower bound and length per dimension, the type of array, and then each item.
- Single dimension arrays with a zero lower bound are special cased as an SZArray operation and don't write out rank or lower bounds just a single length.
- Otherwise types are handled in the following order:
- If the Pickler has an IReducer registered for the object type that is used.
- If the object inherits from
System.MarshalByRefObjectPikala will now explicitly fail. - Otherwise Pikala tries to serialize each field on the object.
IReducer is for reducing a complex object to a simpler one that can be serialized. Pikala has some built-in reducers, such as the one for Dictionary<TKey, TValue> which causes that to be serialized as a KeyValuePair<TKey, TValue>[] rather than trying to serialize the internal bucket structure of a Dictionary.
Pikala is open to PRs for any BCL type that could have a better IReducer than it's default behaviour.
See the security warning at the top of the readme. You should only deserialize data you trust. It is possible to construct malicious data which will execute arbitrary code during deserialization. Never deserialize data that could have come from an untrusted source, or that could have been tampered with.
Consider signing data if you need to ensure that it has not been tampered with. Treat data fed into Pikala the same as you would an executable file to be run.
This project is licensed under the LGPL License - see the LICENSE file for details