Generic type parameter to match anything that is IEnumerable<T>

Viewed 1754

I have the following C# code that does not behave as I would like.

The requirement is that anything that implements any IEnumerable<T> uses the second method that prints "2", but anything else uses the first method that prints "1".

A naive demonstration is below. ICollection<int>, IList<int>, List<int> and int[] all implement IEnumerable<T> but "1" is printed instead of "2"

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

namespace Test
{
    public class Program
    {
        public static void Main()
        {
            var parent = new Parent<Class>();

            // OK: TProperty == int. Prints "1"
            parent.Map(c => c.IntValue);

            // OK: TProperty == int. Prints "2"
            parent.Map(c => c.IEnumerableIntValue);

            // Wrong: TProperty == ICollection<int>. Prints "1"
            parent.Map(c => c.ICollectionIntValue);

            // Wrong: TProperty == List<int>. Prints "1"
            parent.Map(c => c.ListIntValue);

            // Wrong: TProperty == int[]. Prints "1"
            parent.Map(c => c.ArrayIntValue);
        }

        public class Class
        {
            public int IntValue { get; set; }
            public IEnumerable<int> IEnumerableIntValue { get; set; }
            public ICollection<int> ICollectionIntValue { get; set; }
            public List<int> ListIntValue { get; set; }
            public int[] ArrayIntValue { get; set; }
        }
    }

    public class Parent<T>
    {
        public void Map<TProperty>(Expression<Func<T, TProperty>> expression)
        {
            Console.WriteLine("1");
        }

        public void Map<TProperty>(Expression<Func<T, IEnumerable<TProperty>>> expression)
        {
            Console.WriteLine("2");
        }
    }
}

I've tried changing the definition to

public void Map<TEnumerable, TElement>(Expression<Func<T, TEnumerable>> expression) where TEnumerable : IEnumerable<TElement>
{
    Console.WriteLine("2");
}

but this requires explicit type parameters to use, which is unacceptable:

parent.Map<int[], int>(c => c.ArrayIntValue);

Has anyone got an ideas on how to achieve this in C# at compile time? Any ideas are appreciated. Maybe contra/covariant delegates could work? I've tried wrangling with the C# compiler but have got nowhere.

2 Answers
Related