我有以下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#编译器进行争吵但无处可去。
答案 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