为什么编译器无法看到 IEnumerable,当 IReadonlyDictionary 也存在时

huangapple go评论68阅读模式
英文:

Why can't the compiler see IEnumerable<T> when IReadonlyDictionary<U, T> is also present

问题

Sure, here is the translated code portion without any additional content:

我有一个在.NET Framework 4.8中的类,它同时实现了`IEnumerable&lt;T&gt;`和`IReadOnlyDictionary&lt;U, T&gt;`:

```csharp
public class Foo : IEnumerable&lt;Bar&gt;, IReadOnlyDictionary&lt;int, Bar&gt;
{
	public Bar this[int key] =&gt; /* ... */;

	public IEnumerable&lt;int&gt; Keys =&gt; /* ... */;

	public IEnumerable&lt;Bar&gt; Values =&gt; /* ... */;

	public int Count =&gt; /* ... */;

	public bool ContainsKey(int key) =&gt; /* ... */;

	public IEnumerator&lt;Bar&gt; GetEnumerator() =&gt; /* ... */;

	public bool TryGetValue(int key, out Bar value) =&gt; /* ... */;

	IEnumerator IEnumerable.GetEnumerator()	{ /* ... */; }

	IEnumerator&lt;KeyValuePair&lt;int, Bar&gt;&gt; IEnumerable&lt;KeyValuePair&lt;int, Bar&gt;&gt;.GetEnumerator()
    {
        /* ... */;
    }
}

foreach循环中使用这个类是正常的:

var foo = new Foo();
foreach (var bar in foo)
{
    /* ... */
}

但如果我尝试使用IEnumerable&lt;T&gt;的扩展方法,例如

bool b = foo.Any();

我会得到以下错误:

CS1061 'Foo' does not contain a definition for 'Any' and no accessible extension method 'Any' accepting a first argument of type 'Foo' could be found (are you missing a using directive or an assembly reference?)

我的代码中已经包含了using System.Linq,如果我从类中删除IReadOnlyDictionary&lt;int, Bar&gt;,错误就消失了。为什么编译器不能看到IEnumerable&lt;T&gt;,当IReadOnlyDictionary&lt;U, T&gt;也存在时?


<details>
<summary>英文:</summary>

I have a class in .NET Framework 4.8 that implements both `IEnumerable&lt;T&gt;` and `IReadonlyDictionary&lt;U, T&gt;`:

public class Foo : IEnumerable<Bar>, IReadOnlyDictionary<int, Bar>
{
public Bar this[int key] => /* ... */;

public IEnumerable&lt;int&gt; Keys =&gt; /* ... */;

public IEnumerable&lt;Bar&gt; Values =&gt; /* ... */;

public int Count =&gt; /* ... */;

public bool ContainsKey(int key) =&gt; /* ... */;

public IEnumerator&lt;Bar&gt; GetEnumerator() =&gt; /* ... */;

public bool TryGetValue(int key, out Bar value) =&gt; /* ... */;

IEnumerator IEnumerable.GetEnumerator()	{ /* ... */; }

IEnumerator&lt;KeyValuePair&lt;int, Bar&gt;&gt; IEnumerable&lt;KeyValuePair&lt;int, Bar&gt;&gt;.GetEnumerator()
{
    /* ... */;
}

}


Using this class in a `foreach` loop works fine:

var foo = new Foo();
foreach (var bar in foo)
{
/* ... */
}


But if I try to use an extention method for `IEnumerable&lt;T&gt;`, for example

bool b = foo.Any();


I&#39;m getting the error

&gt; CS1061	&#39;Foo&#39; does not contain a definition for &#39;Any&#39; and no accessible extension method &#39;Any&#39; accepting a first argument of type &#39;Foo&#39; could be found (are you missing a using directive or an assembly reference?)

My code already contains `using System.Linq`, and If I remove `IReadOnlyDictionary&lt;int, Bar&gt;` from my class the error goes away. Why can&#39;t the compiler see `IEnumerable&lt;T&gt;` when `IReadonlyDictionary&lt;U, T&gt;` is also present?

</details>


# 答案1
**得分**: 4

The compiler doesn't know whether you want to call `Any()` on:

* `IEnumerable<Bar>`, or
* `IReadOnlyDictionary<int, Bar> : IEnumerable<<KeyValuePair<int, Bar>>>`

So be explicit:

```csharp
IEnumerable<Bar> foo = ...
// or
IReadOnlyDictionary<int, Bar> foo = ...

if (foo.Any())
{
    // ...
}

But yeah, the compiler error looks confusing, it could be clearer about that ambiguity.

英文:

The compiler doesn't know whether you want to call Any() on:

  • IEnumerable&lt;Bar&gt;, or
  • IReadOnlyDictionary&lt;int, Bar&gt; : IEnumerable&lt;&lt;KeyValuePair&lt;int, Bar&gt;&gt;

So be explicit:

IEnumerable&lt;Bar&gt; foo = ...
// or
IReadOnlyDictionary&lt;int, Bar&gt; foo = ...

if (foo.Any())
{
    // ...
}

But yeah, the compiler error looks confusing, it could be clearer about that ambiguity.

答案2

得分: 4

IReadOnlyDictionary&lt;K, V&gt; 继承自 IEnumerable&lt;KeyValuePair&lt;K, V&gt;&gt;。您的类同时实现了 IEnumerable&lt;KeyValuePair&lt;int, Bar&gt;&gt;IEnumerable&lt;Bar&gt;

扩展方法 IEnumerable.Any 是泛型的,并且是对所有 IEnumerable&lt;T&gt; 的扩展。

public static bool Any&lt;TSource&gt; (this System.Collections.Generic.IEnumerable&lt;TSource&gt; source);

编译器无法确定 TSource 应该是 Bar 还是 KeyValuePair&lt;int, Bar&gt;

foreach 没有这个问题,只是因为您隐式地实现了 IEnumerable&lt;Bar&gt;。这使得编译器在成员查找时可以直接找到 Foo 上的 GetEnumerator。如果您以显式方式实现它,如下所示:

IEnumerator&lt;Bar&gt; IEnumerable&lt;Bar&gt;.GetEnumerator() => ...;

foreach 也无法确定您想要什么。按照语言规范中指定的编译器执行步骤,了解这是如何工作的。

除了将其转换为所需的 IEnumerable&lt;T&gt; 实例(就像在 CodeCaster 的答案中一样),您还可以通过指定 Any 的类型参数告诉编译器您想要什么 TSource

bool b = foo.Any&lt;Bar&gt;();
英文:

Since IReadOnlyDictionary&lt;K, V&gt; inherits from IEnumerable&lt;KeyValuePair&lt;K, V&gt;&gt;. Your class implements both IEnumerable&lt;KeyValuePair&lt;int, Bar&gt;&gt; and IEnumerable&lt;Bar&gt;.

The extension method IEnumerable.Any is generic, and is an extension on all IEnumerable&lt;T&gt;s.

public static bool Any&lt;TSource&gt; (this System.Collections.Generic.IEnumerable&lt;TSource&gt; source);

The compiler cannot figure out what TSource should be - whether it should be Bar or KeyValuePair&lt;int, Bar&gt;.

foreach doesn't have this issue only because you implemented IEnumerable&lt;Bar&gt; implicitly. This allows GetEnumerator to be found directly on Foo when the compiler does a member lookup. If you had implemented it explicitly like this:

IEnumerator&lt;Bar&gt; IEnumerable&lt;Bar&gt;.GetEnumerator() =&gt; ...;

foreach wouldn't be able to figure out what you want either. Follow the steps that the compiler takes, specified in the language spec to see exactly how this works.

In addition to casting it to the desired instantiation of IEnumerable&lt;T&gt; like in CodeCaster's answer, you can also tell it what TSource you want by specifying the type arguments of Any:

bool b = foo.Any&lt;Bar&gt;();

huangapple
  • 本文由 发表于 2023年4月4日 18:08:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/75928115.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定