【问题标题】:Why doesn't the following generic method call require a type?为什么下面的泛型方法调用不需要类型?
【发布时间】:2011-04-02 19:24:23
【问题描述】:

抱歉 - 不确定更好的名称,如果您能想到更好的名称,请修改。

我正在尝试了解更多关于 IEnumerable/collections/generics 的知识,并且我认为我已经取得了一些进展,直到这个例子让我明白了:

 var nums = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 10 };

 var result = FilterNums(nums, i => i % 2 != 0);

.....

public static IEnumerable<T1> FilterNums<T1>(IEnumerable<T1> numslist, Func<T1, bool> predicateDelegate)

.....

为什么调用FilterNums 有效?如果我将其更改为FilterNums&lt;int&gt;,它仍然可以工作,这正是我真正希望输入的内容。

那么,是如何检测 lambda 查询的 T1 并且不需要我编写它还是发生了其他事情?

【问题讨论】:

  • var 不是 Variant 类型,就像您在其他语言中可能发现的那样,编译器只是在编译期间为您将其重命名为正确的类型。它通过“类型推断”(参见 Linkgoron 的回答)来做到这一点,在您的示例中,它只能是 int,因此您不需要明确指定。

标签: c# generics syntax lambda


【解决方案1】:

类型推断过程有点复杂;有关详细信息,请参阅 C# 规范的第 7 章。

简而言之,它是这样工作的。

当您调用没有泛型参数列表的方法时,首先我们创建一组该名称的所有可访问方法。

接下来我们检查它们是否是通用的。如果是,那么我们尝试查看是否可以从实际参数中推断出泛型类型参数。推理过程是这样的。假设你有一个参数列表 A:

A: (nums, i => i%2 != 0)

和形式参数类型列表P:

P: (IEnumerable<T1>, Func<T1, bool>)

还有一组泛型类型参数X:

X: <T1>

类型推断的目标是从 A 的每个成员到 P 的相应成员进行推断,以便推断出关于 X 的每个成员的足够信息。

这个特殊的问题很简单。从第一个参数我们看到 nums 的类型是 int[]。我们看到 P 中的第一个形参是IEnumerable&lt;T1&gt;。我们知道 int[] 可以转换为IEnumerable&lt;int&gt;,因此 T1 可能是 int。我们记下了这一事实。

至此,我们基本上完成了。我们无法从第二个参数/参数对推断出关于 T1 的任何信息。类型推断成功,并确定 T1 是整数。所以我们假设你用&lt;int&gt; 作为类型参数列表来调用它。

这是一个非常简单的类型推断问题。考虑一下这个:

A: (customers, c=>c.Name)
P: (IEnumerable<T>, Func<T, R>)
X: <T, R>

这是您在执行customers.Select(c=&gt;c.Name) 时遇到的问题。

我们做什么?从第一个参数我们推断“客户实现IEnumerable&lt;Customer&gt;,因此 T 可能是客户”。 进行该推论之后,我们可以说“lambda 中的 c 因此是 Customer。因此这是一个从 Customer 到 Customer.Name 类型的 lambda。那是字符串。因此 R 是字符串”。

看看在这种情况下如何将一个推理链接到下一个推理上;我们不能简单地“并行”进行推理,因为一个推理可能取决于另一个推理的结果。这些依赖关系可以包含循环和其他奇怪的拓扑。我们如何通过依赖链取得进展的确切细节有点复杂。有关详细信息,请参阅规范。

我们还必须处理为类型参数推导出两个或多个边界的情况,以及这些边界是“上限”、“下限”还是“精确”。

如果您对这个主题感兴趣,我已经写了很多关于它的文章。见

http://blogs.msdn.com/b/ericlippert/archive/tags/type+inference/

关于类型推断各个方面的大量文章,而不仅仅是泛型方法类型推断。有关我解释方法类型推断如何在 C# 3.0 中工作的视频,请参阅:

http://wm.microsoft.com/ms/msdn/visualcsharp/eric_lippert_2006_11/EricLippert01.wmv

【讨论】:

  • 你解释得很好……我想可能是基于T1其他用途的猜测,现在你已经确认了!一切都让我感兴趣,但我还是个新手,我已经为你的链接添加了书签,当我了解更多时会阅读它们。非常感谢。
  • 很抱歉,如果您的某个链接声明了这一点,但是,我觉得我还没有达到可以阅读 C# 规范的水平!我现在完全理解这一点以及它为什么/如何工作 - 我对你的回答感到非常高兴,但我只是想知道最佳实践是什么? .... 目前,我只做最基本的学习示例,我看不到速度上的差异,我真的很喜欢这样的事实,但是,是否有任何开销和/或我应该输入类型如果我提前知道呢?
  • @Wil:规范并没有那么可怕。您可以免费下载它,或者如果您购买印刷版,它充满了来自 C# 专家的关于该语言的有趣事实的注释。这里的最佳实践是做任何你认为正确的事情(1) - 如果类型推断出错,那么你必须输入正确的类型,以及(2)任何更容易阅读的东西。有时在那里有类型会使代码更清晰,有时它是这个丑陋的东西卡在原本干净的代码中间。我倾向于将其排除在外,让推理来完成工作。
  • 你是对的!只是快速浏览了可下载的副本......我认为这会更难理解!至于注释,你说的是这本书吗? amazon.com/dp/0321741765 - 就个人而言,最近才学习泛型,它确实让我感到困惑 - 但我也认为在某些情况下更容易阅读,查看你的个人资料/工作,我认为跟随你的对我来说是安全的建议并在有意义时将其排除在外! :) 非常感谢!
【解决方案2】:

这是因为类型推断。

编译器知道您正在发送一个 int,因此它隐式地将泛型类型设置为 int。如果您查看智能感知,您会看到您的泛型类型设置为 int。

【讨论】:

  • 我知道我可能听起来很愚蠢,但它仍然让我有点困惑......它如何理解我正在向它发送一个 int? ....然后查看第一个 IEnumerable numslist 并将 T1 设置在那里或其他地方?
  • @Wil stackoverflow.com/questions/479883/… 这可能会对您有所帮助。
猜你喜欢
  • 2011-08-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-14
  • 2015-10-15
  • 2015-12-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多