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.