Compilation error when i used lambda creator with base type for `Where` condition in linq

I have some part of code. It's not real code but very similar with my problem in production. Call of ASupplier in point 2 not compiled cause result of var filtered is List<IA>. It's look's like valid cause Where declared as

public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)

But i can't understand why point 3 is valid because FixedIACondition declaration similar with result of call IAConditionCreator

using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqWhereConditionProblem
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            var collection = new List<A>();

            ASupplier(collection); // 1) OK

            var filtered = collection.Where(IAConditionCreator("a")).ToList();
            ASupplier(filtered); // 2) NOT OK

            var filtered2 = collection.Where(FixedIACondition).ToList();
            ASupplier(filtered2); // 3) OK
        }

        private static void ASupplier(IReadOnlyCollection<A> aCollection)
        {
            foreach (var a in aCollection)
            {
                Console.WriteLine(a.GetText());
            }
        }

        private static Func<IA, bool> IAConditionCreator(string value)
        {
            return a => a.GetText() == value;
        }

        private static bool FixedIACondition(IA ia) => ia.GetText() == "aa";
    }

    public interface IA
    {
        string GetText();
    }

    public class A : IA
    {
        public string GetText()
        {
            return "ABC";
        }
    }
}

Upvotes: 4

Views: 81

Answers (2)

Ortiga
Ortiga

Reputation: 8824

Changing method ASupplier to receive IASupplier resolves this issue:

private static void ASupplier(IReadOnlyCollection<IA> aCollection)
{
    foreach (var a in aCollection)
    {
        Console.WriteLine(a.GetText());
    }
}

If you only care about the interface, this may be what you want. If ASupplier is solely to handle type A, you can either Cast or filter from the list those OfType A

ASupplier(filtered.OfType<A>().ToList()); // this will filter the list to return those of type A

ASupplier(filtered.Cast<A>().ToList()); // this will throw exception if any object can't be cast to A

Another option is casting the Func itself to return the type you want:

var condition = (Func<A, bool>)IAConditionCreator("a");
var filtered = collection.Where(condition).ToList();
ASupplier(filtered);

There are other ways to handle polymorphism, but then you'll have to be more specific about your scenario.

Upvotes: 0

Georg
Georg

Reputation: 5791

The way the compiler does implicit type conversion is different for delegate objects and method groups and I guess its main goal is to reduce the number of type conversions (including implicit type conversions) as far as possible.

The method FixedIACondition is referenced as a method group that the compiler has to convert to a delegate object anyhow. The compile is able to infer that this method group can be converted into a Func<A, bool>, which is the type required by .Where.

For IAConditionCreator, the compiler already has a delegate object and now tries to fit that object into the call. However, it has to convert either the collection to IEnumerable<IA> or to convert the delegate to Func<A, bool>. The support for covariant delegates is not that good in .NET (try calling Delegate.Combine) and I guess the compiler team is aware of that and thus tries to avoid this and rather go for the first conversion.

Upvotes: 3

Related Questions