mbraceproject / FsPickler

A fast multi-format message serializer for .NET
http://mbraceproject.github.io/FsPickler/
MIT License
325 stars 52 forks source link

Is it possible to use a custom pickler for `System.Collections.Immutable.ImmutableList` ? #128

Open marklam opened 1 year ago

marklam commented 1 year ago

Here's what I've tried:

#r "nuget:FsPickler"

open System.IO
open System.Collections.Immutable
open MBrace.FsPickler

let mkPickler<'t> (resolver : IPicklerResolver) =
    let seqPickler = resolver.Resolve<'t seq> ()

    let writer (w : WriteState) (ns : ImmutableList<'t>) =
        seqPickler.Write w "value" (ns :> 't seq)

    let reader (r : ReadState) : ImmutableList<'t> =
        ImmutableList.CreateRange<'t>(seqPickler.Read r "value")

    Pickler.FromPrimitives(reader, writer)

let resolver : IPicklerResolver =
    let registry = CustomPicklerRegistry()
    registry.RegisterFactory mkPickler
    PicklerCache.FromCustomPicklerRegistry registry

let binarySerializer = FsPickler.CreateBinarySerializer(picklerResolver = resolver)

let storeAndCount value =
    use s = new MemoryStream()
    binarySerializer.Serialize (s, value)
    s.ToArray().Length

let items = seq { 1 .. 100 }
let immutableList = ImmutableList.CreateRange items

printfn "Storing items gives %d bytes" (storeAndCount items)
printfn "Storing ImmutableList gives %d bytes" (storeAndCount immutableList)

But the ImmutableList doesn't seem to trigger my serializer:

Storing items gives 213 bytes
MBrace.FsPickler.NonSerializableTypeException: Type 'System.Collections.Immutable.ImmutableList`1[System.Int32]' is not serializable.
   at MBrace.FsPickler.Utils.Exn`1.get_Value() in C:\Users\eitsarpa\devel\mbrace\FsPickler\src\FsPickler\Utils\Utils.fs:line 59
   at MBrace.FsPickler.PicklerCache.MBrace-FsPickler-IPicklerResolver-Resolve[T]() in C:\Users\eitsarpa\devel\mbrace\FsPickler\src\FsPickler\PicklerGeneration\PicklerCache.fs:line 75
   at MBrace.FsPickler.FsPicklerSerializer.Serialize[T](Stream stream, T value, FSharpOption`1 pickler, FSharpOption`1 streamingContext, FSharpOption`1 encoding, FSharpOption`1 leaveOpen) in C:\Users\eitsarpa\devel\mbrace\FsPickler\src\FsPickler\FsPickler\Serializer.fs:line 60
   at FSI_0004.storeAndCount[a](a value) in C:\Users\markl\AppData\Local\Temp\uw5uvhcg..fsx:line 27
   at <StartupCode$FSI_0004>.$FSI_0004.main@() in C:\Users\markl\AppData\Local\Temp\uw5uvhcg..fsx:line 34
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
Stopped due to error
ingted commented 8 months ago

You need to provide the generic type parameter:


open System.IO
open System.Collections.Immutable
open MBrace.FsPickler

let mkPickler<'t> (resolver : IPicklerResolver) =
    let tPickler = resolver.Resolve<'t> ()
    let intPickler = resolver.Resolve<int> ()

    let writer (w : WriteState) (ns : ImmutableList<'t>) =
        let tArr = ns |> Seq.toArray
        intPickler.Write w "cnt" tArr.Length
        tArr
        |> Array.iter (fun t ->
            tPickler.Write w "value" t
        )

    let reader (r : ReadState) : ImmutableList<'t> =
        let l = intPickler.Read r "cnt"
        let tArr = 
            [|0..(l-1)|]
            |> Array.map (fun _ -> 
                tPickler.Read r "value"
            )
        ImmutableList.CreateRange<'t> tArr

    Pickler.FromPrimitives(reader, writer)

let resolver : IPicklerResolver =
    let registry = CustomPicklerRegistry()
    registry.RegisterFactory mkPickler
    PicklerCache.FromCustomPicklerRegistry registry

let p = mkPickler<int> resolver

let binarySerializer = FsPickler.CreateBinarySerializer() //picklerResolver = resolver)

let storeAndCount value =
    use s = new MemoryStream()
    binarySerializer.Serialize (s, value, p)
    s.ToArray().Length

let items = seq { 1 .. 100 }
let immutableList = ImmutableList.CreateRange items

printfn "Storing ImmutableList gives %d bytes" (storeAndCount immutableList)

let resolver2 : IPicklerResolver =
    let registry = CustomPicklerRegistry()
    registry.RegisterFactory mkPickler<int>
    PicklerCache.FromCustomPicklerRegistry registry

let binarySerializer2 = FsPickler.CreateBinarySerializer(picklerResolver = resolver2)

let storeAndCount2 value =
    use s = new MemoryStream()
    binarySerializer2.Serialize (s, value)
    s.ToArray().Length

printfn "Storing items gives %d bytes" (storeAndCount2 items)
printfn "Storing ImmutableList gives %d bytes" (storeAndCount2 immutableList)
ingted commented 8 months ago

image

ingted commented 7 months ago
    /// Registers a user-specified pickler factory
    member __.RegisterFactory<'T>(factory : IPicklerResolver -> Pickler<'T>) : unit =
        registerPickler (fun () -> picklerFactories.[typeof<'T>] <- fun r -> factory r :> Pickler)