【问题标题】:How can I return <TEnumerable, T> : where TEnumerable:IEnumerable<T>如何返回 <TEnumerable, T> :其中 TEnumerable:IEnumerable<T>
【发布时间】:2018-12-04 19:48:06
【问题描述】:

目标:泛型枚举类型返回时为同一类型。

注意:这在输入类型时有效,但我不明白为什么不能推断它们。

List&lt;T&gt; 然后返回List&lt;T&gt;

IOrderedEnumerable&lt;T&gt; 然后返回IOrderedEnumerable&lt;T&gt;

ETC

当前方法(仅在输入所有类型时有效)

public static TEnumerable WithEach<TEnumerable, T>(this TEnumerable items, Action<T> action)
where TEnumerable : IEnumerable<T>
{
    foreach (var item in items) action.Invoke(item);
    return items;
}

仅作为示例

var list = new List<int>(); //TODO: Mock random values
list.WithEach(x => Console.WriteLine(x)) //Here WithEach ideally returns List<int> following orignal type List<int>
    .OrderBy(x => x) 
    .WithEach(x => Console.WriteLine(x)); //Here WithEach ideally returns IOrderedEnumerable<int> following OrderBy

让它发挥作用

var list = new List<int>(); //TODO: Mock random values
list.WithEach<List<int>, int>(x => Console.WriteLine(x))
    .OrderBy(x => x) 
    .WithEach<IOrderedEnumerable<int>, int>(x => Console.WriteLine(x));

我缺少的是为什么 C# 不能推断类型,尽管 where 过滤器确实使类型准确。我理解您为什么要为方法提供全部或不提供泛型类型,所以请不要向我指出这些答案。

编辑:如果我无法推断类型;那我怎样才能让它更优雅呢?

【问题讨论】:

  • 我会研究细节,但只是作为一个警告:这最终会迭代序列,然后返回相同的序列,可能不支持被迭代多次。在设计类似 LINQ 的方法时,我尽量避免多次迭代。在你的情况下这可能不是问题,但我想我会在研究类型推断之前提到它。
  • 你是对的@JonSkeet;理想情况下,迭代是不可变的。我只是在摸索使方法流利并保持问题简单。
  • 为什么不yield
  • @KennethK.:鉴于声明的目标是“通用枚举类型在返回时是相同的类型”。这听起来像是对您问题的回答。 (例如,在调用OrderBy 然后WithEach 之后,使用OP 的设计,他们可以调用ThenBy - 如果WithEach 只返回IEnumerable&lt;T&gt;,他们就不能。)
  • @Servy:我想说的是,为什么类型也没有被推断出来是有区别的——这个问题在 anywhere 中没有第二个类型参数参数列表;确实如此,但该论点无助于推断。对于类型推断,我认为有多个有点相似但有细微差别的问题很有用。

标签: c# extension-methods generic-collections


【解决方案1】:

C# 中的类型推断非常很复杂 - 仅此一次,我不会拿出规范来尝试逐步完成它,因为我知道它有多可怕成为。

相信问题在于参数/参数组合都没有为编译器提供足够的信息来推断T

  • TEnumerable items 参数未提及T,因此尽管有类型约束,但它不用于推断T
  • Action&lt;T&gt; 参数可以,但编译器无法根据您提供的 lambda 表达式进行推断

我想不出对方法签名有什么好的改变,正是你的第一个代码工作 - 但你可以改变你调用方法的方式只是一点点 让它工作,通过在 lambda 表达式中指定参数类型:

var list = new List<int>();
list.WithEach((int x) => Console.WriteLine(x++))
    .OrderBy(x => x) 
    .WithEach((int x) => Console.WriteLine(x));

当然,它的缺点是它不适用于匿名类型。

针对该缺点的一种解决方法是一个非常可怕的解决方法,但它可以让您在需要时通过参数来表达T 的类型。您将方法签名更改为:

public static TEnumerable WithEach<TEnumerable, T>(
    this TEnumerable items,
    Action<T> action,
    T ignored = default(T))

如果你想用一些匿名类型的列表调用方法,你可以这样写:

list.WithEach(x => Console.WriteLine(x.Name), new { Name = "", Value = 10 });

... 最后一个参数将匹配匿名类型。这将允许T 的类型由最终参数而不是第二个参数来推断。当然,您可以将其用于其他类型,但我可能会坚持将其用于匿名类型。

这是一个非常可怕的 hack,我认为我实际上不会使用它,但如果你真的非常需要它来处理匿名类型,它会应付。 p>

【讨论】:

  • 你的想法当然是正确的。基本上所有推论都必须从参数到形式,然后然后检查约束。我们从不根据约束进行推断。我们根据参数进行推理,然后验证它们与约束一致。
  • @EricLippert 那是有道理的。赞赏!
  • 在推断委托类型的形式参数列表中的类型参数的情况下,该规则(形式参数)略微放松。在这种情况下,正如您正确指出的那样,我们从 类型化 lambdas 的形式参数列表 推断为 具有未固定类型参数的委托的形式参数列表
  • @MichaelPuckettII:不客气。无论出于何种原因,这都是一个有争议的决定;很多人认为应该使用约束来帮助编译器做出决定,而不是我认为的那样,即应该使用约束来让开发人员知道什么时候推理不够清晰我们无法从论点中确定正确的答案。关于这个问题的长期、徒劳的辩论,请参阅blogs.msdn.microsoft.com/ericlippert/2009/12/10/…的cmets@
  • @MichaelPuckettII: 仅供参考,Jon 的“可怕的 hack”是一种有时被称为“实例转换”的技术,因为该技术的原始用例用于进行 转换采用了您想要转换为的类型的 exampleblogs.msdn.microsoft.com/alexj/2007/11/22/…
【解决方案2】:

仅使用 T 声明您的扩展程序,如下所示:

public static IEnumerable<T> WithEach<T>(this IEnumerable<T> items,Action<T> action)
{
    foreach (var item in items) action.Invoke(item);
    return items;
}

这样做的缺点是丢失了您实现的 IEnumerable 的特定子类。

为您关心的特定子类实现重载很容易:

public static IOrderedEnumerable<T> WithEach<T>(this IOrderedEnumerable<T> items, Action<T> action)
{
    ((IEnumerable<T>)items).WithEach(action);
    return items;
} 

不过,在迭代后返回 IEnumerable 有点吓人。 IEnumerables 可能无法重新启动。

【讨论】:

  • 重点(据我所知)是 OP 希望能够返回与第一个参数相同的类型,以便他们可以继续使用特定于该类型的方法(而不是 @ 987654324@) 稍后。
  • 虽然这将返回相同的类型引用,但它不会是相同的类型。例如,当我希望它是 IOrderedEnumerable&lt;T&gt; 时,这将返回 IEnumberable&lt;T&gt;
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-08-05
  • 2012-08-28
  • 1970-01-01
  • 2012-10-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多