用于匹配任何IEnumerable <T>的通用类型参数

H B*_*amy 6 c# generics type-inference

我有以下C#代码不符合我的意愿.

要求是任何实现any IEnumerable<T>"2"东西都使用第二种打印方法,但其他任何东西都使用第一种打印方法"1".

下面是一个简单的演示.ICollection<int>,IList<int>,List<int>并且int[]都实现IEnumerable<T>,但"1"打印代替"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");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我已经尝试将定义更改为

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

但这需要使用显式类型参数,这是不可接受的:

parent.Map<int[], int>(c => c.ArrayIntValue);
Run Code Online (Sandbox Code Playgroud)

有没有人有关于如何在编译时在C#中实现这一点的想法?任何想法都表示赞赏.也许反对/协变代表可以工作?我曾尝试与C#编译器争吵,但无处可去.

InB*_*een 1

更新我之前的答案是完全错误的,没有正确思考。

不,你不能这样做。原因是它T总是比IEnumerable<T>任何非静态类型的东西更好的匹配IEnumerable<T>,这就是泛型的工作原理;T除非有竞争性的精确匹配,否则没有比这更好的通用匹配了。

考虑以下:

void Foo<T>(T t) { }
void Foo<T>(IEquatable<T> equatable) { }
Run Code Online (Sandbox Code Playgroud)

您真的希望Foo(1)解决第二个过载问题吗?

或者Foo("hello")决定Foo<char>(IEnumerable<char>)何时适用的候选人是:

void Foo<T>(T t) { }
void Foo<T>(IEnumerable<T> enumerable) { }
Run Code Online (Sandbox Code Playgroud)

最简单的解决方案是在映射时进行显式强制转换:

parent.Map(c => c.ICollectionIntValue.AsEnumerable());
parent.Map(c => c.ListIntValue.AsEnumerable());
//etc.
Run Code Online (Sandbox Code Playgroud)

可以dynamic按照以下方式做一些奇特的混合一些反射的事情:

public void Map<TProperty>(Expression<Func<T, TProperty>> expression)
{
    var genericInterfaces = typeof(TProperty).GetInterfaces().Where(i => i.IsGenericType);
    var iEnumerables = genericInterfaces.Where(i => i.GetGenericTypeDefinition().Equals(typeof(IEnumerable<>))).ToList();

    if (iEnumerables.Count > 1)
        throw new InvalidOperationException("Ambiguous IEnumerable<>");

    var iEnumerable = iEnumerables.FirstOrDefault();

    if (iEnumerable == null)
    {
        Console.WriteLine("1");
    }
    else
    {
        //ok, we know we have an IEnumerable of something. Let the runtime figure it out.
        Expression<Func<T, IEnumerable<dynamic>>> newExpression = e => expression.Compile()(e) as IEnumerable<dynamic>;
        Map(newExpression);
    }
}

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