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

时间:2017-07-11 12:05:56

标签: c# generics type-inference

我有以下C#代码,表现不像我想要的那样。

要求是任何实现任何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");
        }
    }
}

我已尝试将定义更改为

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

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

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

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

2 个答案:

答案 0 :(得分:2)

真正令人惊讶的是,编译器明确确定其类型参数为IEnumerable<T>的唯一方法是实际处理TProperty的方法吗?

这是一个未经优化的实现,它动态地确定类型IEnumerable<>是否明确地实现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) { if (ReflectionHelpers.IsUnambiguousIEnumerableOfT(typeof(TProperty))) { MapMany(expression); } else { MapOne(expression); } } void MapOne(Expression expression) { Console.WriteLine("1"); } void MapMany(Expression expression) { Console.WriteLine("2"); } } static class ReflectionHelpers { public static bool IsUnambiguousIEnumerableOfT(Type type) { // Simple case - the type *is* IEnumerable<T>. if (IsIEnumerableOfT(type)) { return true; } // Harder - the type *implements* IEnumerable<T>. HashSet<Type> distinctIEnumerableImplementations = new HashSet<Type>(); ExtractAllIEnumerableImplementations(type, distinctIEnumerableImplementations); switch (distinctIEnumerableImplementations.Count) { case 0: return false; case 1: return true; default: // This may or may not be appropriate for your purposes. throw new NotSupportedException("Multiple IEnumerable<> implementations detected."); } } private static bool IsIEnumerableOfT(Type type) { return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>); } private static void ExtractAllIEnumerableImplementations(Type type, HashSet<Type> implementations) { foreach (Type interfaceType in type.GetInterfaces()) { if (IsIEnumerableOfT(interfaceType)) { implementations.Add(interfaceType); } ExtractAllIEnumerableImplementations(interfaceType, implementations); } } } } 接口的一个(且只有一个)封闭版本,允许您以不同方式处理表达式树那个特例。

List<List<Integer>> permutationS = new ArrayList<>();

// list initialization here

int i = 5; // the Integer you are searching for

for(List<Integer> innerList : permutationS) {
   if(innerList.contains(i)) {
     // found, do something with innerList
   }
}

答案 1 :(得分:1)

更新我之前的回答是完全错误的,没有正确认为。

不,你不能这样做。原因是T对于任何非静态类型为IEnumerable<T>的内容,总是比IEnumerable<T>更好,这就是泛型如何工作;没有比T更好的通用匹配,除非您有竞争完全匹配。

请考虑以下事项:

void Foo<T>(T t) { }
void Foo<T>(IEquatable<T> equatable) { }

您真的希望Foo(1)解决第二次重载吗?

当适用的候选人是:{/ p>时,让Foo("hello")解析为Foo<char>(IEnumerable<char>)

void Foo<T>(T t) { }
void Foo<T>(IEnumerable<T> enumerable) { }

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

parent.Map(c => c.ICollectionIntValue.AsEnumerable());
parent.Map(c => c.ListIntValue.AsEnumerable());
//etc.

dynamic