Search code examples
c#linqdictionarycasting

Using Linq to objects, ToDictionary method not satisfying type requirement


I need to pass a variable of type IDictionary<Guid, IEnumerable<Guid>> into a method. When I use the following code:

var userGroupDictionary = _selectedUsers.ToDictionary(u => u.UserGuid, u => u.GroupUsers.Select(gu => gu.Group.GroupGuid).ToList());

I get the following error:

cannot convert from System.Collections.Generic.Dictionary<System.Guid,System.Collections.Generic.List<System.Guid>>' to 'System.Collections.Generic.IDictionary<System.Guid,System.Collections.Generic.IEnumerable<System.Guid>>'

When I iterate through my collection it works however:

var userGroupDictionary = new Dictionary<Guid, IEnumerable<Guid>>();
foreach (var user in _selectedUsers.Where(user => !userGroupDictionary.ContainsKey(user.UserGuid)))
{
    userGroupDictionary.Add(user.UserGuid, user.GroupUsers.Select(gu => gu.Group.GroupGuid).ToList());
}

Any ideas what's going on? Is the compiler unable to tell that Dictionary<Guid, List<Guid>> satisfies IDictionary<Guid, IEnumerable<Guid>>


Solution

  • The problem is that type inference is returning a Dictionary<Guid, List<Guid>>, which is the wrong type for the method you want to call. There's a bit more to it than that, but it involves a type variance discussion. You can solve the problem with the AsEnumerable extension:

    var userGroupDictionary = _selectedUsers
        .ToDictionary(
            u => u.UserGuid,
            u => u.GroupUsers.Select(gu => gu.Group.GroupGuid).ToList().AsEnumerable());
    

    ...or you can do a simple cast, as in the following:

    var userGroupDictionary = _selectedUsers
        .ToDictionary(
            u => u.UserGuid,
            u => (IEnumerable<Guid>)u.GroupUsers.Select(gu => gu.Group.GroupGuid).ToList());
    

    If you merely omit the ToList() as others have recommended, the type inference will indeed render the type you expect, but the IEnumerable returned will have deferred-execution which may or may not be an issue for you. Leaving ToList in there but doing a cast of sorts will immediately evaluate the items instead of lazily evaluating them at the time of iteration.

    For more detail on deferred execution, see the "laziness" (think lazy-evaluation) section of Jon Skeet's Edulinq series, part 44