Skip to content

ILookup<TKey, TElement> does not round trip properly for missing keys #1431

@joelverhagen

Description

@joelverhagen

Bug description

In System.Linq the default ILookup<TKey, TElement> implementation returns an empty sequence when the provided key does not exist, per https://docs.microsoft.com/en-us/dotnet/api/system.linq.lookup-2.item?view=net-6.0#remarks.

However, when you roundtrip an ILookup<TKey, TElement> through MessagePack you end up with an instance that throws a KeyNotFoundException when the key does not exist.

Repro steps

using MessagePack;

var lookup = new[] { "foo" }.ToLookup(x => x);
var mc = new MyClass { Lookup = lookup };
byte[] bytes = MessagePackSerializer.Serialize(mc);
MyClass mc2 = MessagePackSerializer.Deserialize<MyClass>(bytes);

Console.WriteLine("Original: " + mc.Lookup["bar"].Count());
Console.WriteLine("Deserialized: " + mc2.Lookup["bar"].Count());

[MessagePackObject]
public class MyClass
{
    [Key(0)]
    public ILookup<string, string> Lookup { get; set; }
}

Expected behavior

This repro program should show:

Original: 0
Deserialized: 0

Actual behavior

Instead, it shows:

Original: 0
Unhandled exception. System.Collections.Generic.KeyNotFoundException: The given key 'bar' was not present in the dictionary.
   at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   at MessagePack.Formatters.Lookup`2.get_Item(TKey key)
   at Program.<Main>$(String[] args) in C:\Users\joelv\Desktop\MessagePackRepro\MessagePackRepro\Program.cs:line 9

C:\Users\joelv\Desktop\MessagePackRepro\MessagePackRepro\bin\Debug\net6.0\MessagePackRepro.exe (process 39896) exited with code -532462766.
  • Version used: 2.3.85
  • Runtime: .NET 6

Additional context

You can do a workaround with Contains but this requires two lookups.

using MessagePack;

var lookup = new[] { "foo" }.ToLookup(x => x);
var mc = new MyClass { Lookup = lookup };
byte[] bytes = MessagePackSerializer.Serialize(mc);
MyClass mc2 = MessagePackSerializer.Deserialize<MyClass>(bytes);

Console.WriteLine("Original: " + mc.Lookup["bar"].Count());
Console.WriteLine("Deserialized: " + (!mc2.Lookup.Contains("bar") ? 0 : mc2.Lookup["bar"].Count()));

[MessagePackObject]
public class MyClass
{
    [Key(0)]
    public ILookup<string, string> Lookup { get; set; }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions