Home > front end >  Weird compiler error in generic class using the Linq ExceptBy function
Weird compiler error in generic class using the Linq ExceptBy function

Time:06-01

I have the following code (distilled from my production code), which should compile but does not:

    public class TestClass
    {
        public abstract class LocalizableEntity<TLocalizedEntity> where TLocalizedEntity : class, ILocalizedData, new()
        {
            public abstract string Id { get; set; }  // Primary key
            public abstract List<TLocalizedEntity> LocalizedData { get; set; }
        }

        public interface ILocalizedData
        {
            string CultureName { get; set; }
        }

        public void MergeData<TEntity, TLocalizedEntity>(IEnumerable<TEntity> newItems)
            where TEntity : LocalizableEntity<TLocalizedEntity>
            where TLocalizedEntity : class, ILocalizedData, new()
        {
            // Get the existing items
            var existingItems = new List<TEntity>();

            // Get the deleted items
            var deletedItems = existingItems
                .ExceptBy(newItems, x => x.Id, StringComparer.InvariantCultureIgnoreCase);
        }
    }

The problem is that the Linq ExceptBy function chokes on the generic input types. As written, that line gives me a compiler error that highlights ExceptBy and says it can't infer the generic type parameters from the usage.

The type arguments for method 'Enumerable.ExceptBy<TSource, TKey>(IEnumerable<TSource>, IEnumerable<TKey>, Func<TSource, TKey>, IEqualityComparer<TKey>?)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

If I modify the offending statement to specify the generic Types I get a different error:

var deletedItems = existingItems
    .ExceptBy<TEntity, string>(newItems, x => x.Id, StringComparer.InvariantCultureIgnoreCase);

Now the compiler highlights the existingItems variable and complains:

'List' does not contain a definition for 'ExceptBy' and the best extension method overload 'Queryable.ExceptBy<TEntity, string>(IQueryable<TEntity>, IEnumerable<string>, Expression<Func<TEntity, string>>, IEqualityComparer<string>?)' requires a receiver of type 'IQueryable'

Ok, I don't know what the "receiver" is in this error message, but it seems to be complaining about existingItems not implementing IQueryable, so I add .AsQueryable like this:

var deletedItems = existingItems
    .AsQueryable()
    .ExceptBy<TEntity, string>(newItems, x => x.Id, StringComparer.InvariantCultureIgnoreCase);

And now it thinks that newItems is an IEnumerable<string> (not an IEnumerable<TEntity>) and it complains that it can't convert from IEnumerable<TEntity> to IEnumerable<string>

I give up. Am I doing something horribly wrong, or is the compiler just confused by the complex generic types?

CodePudding user response:

ExceptBy expects the second parameter (first after this source enumerable) to be a list of keys, not of source objects.

So you need the following

var deletedItems = existingItems
    .ExceptBy(newItems.Select(x => x.Id), x => x.Id, StringComparer.InvariantCultureIgnoreCase)

dotnetfiddle

You can write an extension function that will do the same

public static IEnumerable<TSource> ExceptByKey<TSource, TKey> (
    this IEnumerable<TSource> first,
    IEnumerable<TSource> second,
    Func<TSource, TKey> keySelector,
    IEqualityComparer<TKey>? comparer = null)
  => first.ExceptBy(second.Select(keySelector), keySelector, comparer);
  • Related