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)
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);