【问题标题】:Making use of polymorphism in a generic parameter在泛型参数中使用多态性
【发布时间】:2015-10-20 08:26:59
【问题描述】:

问题

我有一个扩展方法,它扩展 IEnumerable<T> 并采用 Expression 导航到本身必须是 IEnumerable 的属性。

/// <summary>
/// Identify a child collection to search on
/// </summary>
/// <param name="source">source data on which to search</param>
/// <param name="property">Enumerable properties to search.</param>
public static IEnumerable<TSource> Search<TSource, TProperty>(
                             this IEnumerable<TSource> source, 
                             Expression<Func<TSource, IEnumerable<TProperty>> property)
{
    // Do stuff...
}

当子属性定义为IEnumerable&lt;T&gt;时,上面的效果很好,可以如下调用:

var result = shops.Search(s => s.ProductEnumerable);

然而如果子属性是ICollection&lt;T&gt;IList&lt;T&gt;或任何实现IEnumerable&lt;T&gt;的东西,它目前找不到这个方法

另一个关键点是我希望能够在不明确定义类型的情况下使用该方法。

var result = shops.Search(s => s.ProductList);    
//NOT
var result = shops.Search<Shop, Product>(s => s.ProductList);

我尝试过的

尝试 1

我想我可以创建一个新的泛型 (TCollection) 并限制 TCollectionIEnumerable&lt;TProperty&gt; 的位置。

public static IEnumerable<TSource> Search<TSource, TCollection, TProperty>(
                             this IEnumerable<TSource> source, 
                             Expression<Func<TSource, TCollection>> property)
    where TCollection : IEnumerable<TProperty>

这失败了,因为代码无法再找到该方法。

尝试 2

然后我认为我可以将整个第二个参数设为泛型并对其进行正确的约束。

public static IEnumerable<TSource> Search<TSource, TCollection, TProperty>(
                             this IEnumerable<TSource> source, 
                             TCollection property) 
    where TCollection : Expression<Func<TSource, IEnumerable<TProperty>>

这也失败并出现以下错误:

不能使用密封类Expression&lt;TDelegate&gt; 作为类型参数约束。


有没有办法实现我想要的,或者我只需要为所有实现 IEnumerable 的接口创建重载???

提前感谢您花时间阅读这篇冗长的说明

【问题讨论】:

  • Search 方法应该做什么?
  • 当您说however it currently does not find this method if the child property is... 时,您能详细说明您的意思吗?正如下面的答案所示,目前尚不清楚您实际遇到了什么问题。
  • 好问题....再次查看代码后,当我将属性更改为 ICollection 时,我收到一条错误消息,告诉我问题在于它正在尝试使用不同的Search 的过载。我会更新我的问题。谢谢
  • ICollection 不继承 IEnumerable&lt;T&gt; - 对于 非泛型 类型,您肯定需要不同的重载。
  • ICollection&lt;T&gt; 确实 - https://msdn.microsoft.com/en-us/library/92t2ye13(v=vs.110).aspx。这里的 cmets 和答案已经意识到我真正的问题,所以我会尝试自己解决这个问题,但是如果我无法解决问题,我很可能会创建一个新问题

标签: c# generics lambda expression extension-methods


【解决方案1】:

嗯,不,您显示的代码可以正常工作。问题一定在于您如何在搜索方法中使用 IEnumerable&lt;TProperty&gt; - 请记住,因为它是 Func 中的返回值,它必须是协变的。

实际可编译的一段代码:

void Main()
{
  var enumerable = default(IEnumerable<MyItem>);

  Search(enumerable, i => i.MyEnumerable);
  Search(enumerable, i => i.MyCollection);
}

public static IEnumerable<TSource> Search<TSource, TProperty>
  (
    IEnumerable<TSource> source, 
    Expression<Func<TSource, IEnumerable<TProperty>>> property)
{
    return null;
}

public class MyItem
{
  public IEnumerable<string> MyEnumerable { get; set; }
  public ICollection<string> MyCollection { get; set; }
}

编译运行时没有错误。

【讨论】:

  • 我刚刚通过mocking out the code得出了同样的结论
  • 嗯.. 感谢您的回复。让我看看你的和我的有什么不同......! +1
  • 谢谢你。这让我意识到问题不在于这部分代码。问题的存在是因为另一个冲突的过载。
  • @NinjaNye 好的,这是有道理的。 C# 的泛型在这种方式上非常有限——尤其是重载非常棘手。简而言之,没有基于类型约束的 no 重载决议。所以重载决议是在忽略类型约束的情况下完成的,这意味着TPropertyIEnumerable&lt;TProperty&gt; 更合适(遵循通常的重载决议规则)。如果您删除类型约束,这很容易看出 - 代码编译,但它是调用的第二个重载。您可能认为IEnumerable&lt;TProperty&gt; 是更好的选择,但在其他情况下会更糟。
  • @NinjaNye 最后,我认为您有两种选择 - 一种,您可以使用您的规则创建一种可以动态执行重载解析的全局方法。一个简单的if (property.Type.GetInterfaces().Any(i =&gt; i.IsGenericType &amp;&amp; i.GetGenericTypeDefinition() == typeof(IEnumerable&lt;&gt;))) 应该可以正常工作。当然,您会丢失一些编译时的细节,但这无法真正避免(dynamic 非常有助于避免低效地使用反射)。第二,为不同的方法使用不同的名称 - 根据您的具体情况,这可能是首选解决方案。
猜你喜欢
  • 1970-01-01
  • 2021-05-11
  • 1970-01-01
  • 2023-03-29
  • 1970-01-01
  • 2011-07-10
  • 1970-01-01
  • 1970-01-01
  • 2023-03-05
相关资源
最近更新 更多