对于此类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Wrapper <T >
{
public Wrapper (T value)
{
Value = value;
}
public T Value { get; }
public static implicit operator Wrapper <T >(T value)
{
return new Wrapper <T >(value);
}
} |
此代码段无法编译:
1 2
| IEnumerable <int> value = new [] { 1, 2, 3 };
Wrapper <IEnumerable <int>> wrapper = value; |
error CS0266: Cannot implicitly convert type 'System.Collections.Generic.IEnumerable< int>' to 'Spikes.Implicit.Wrapper< System.Collections.Generic.IEnumerable< int>>'. An explicit conversion exists (are you missing a cast?)
但编译器对此非常满意:
1
| Wrapper <IEnumerable <int>> wrapper = new [] { 1, 2, 3 }; |
为什么?
- 这感觉像是一个共同/矛盾的问题。我的知识还不足以写出一个答案,但我很难理解。
- @扁平器CO/反向?左边的字体和右边的完全一样。
- 这不是方差的问题,甚至不是一般性的问题。interface IFoo {}; class Bar : IFoo {}; IFoo x = new Bar(); Wrapper xx = x;将触发同一个问题,如果x改为Bar则问题消失。问题是更基本的事实,即接口不允许进行这种转换。
- 第10.10.3节:"不允许用户定义的转换从接口类型转换为接口类型。特别是,此限制确保在转换为接口类型时不会发生用户定义的转换,并且只有在被转换的对象实际实现指定的接口类型时,才能成功转换为接口类型。"
- 顺便说一下,你真正的问题是,你提供了这个转换(对于任何一个T)作为implicit。这是个坏主意,即使你能让它工作。您可以使它成为一个显式转换,但最好只提供一个Wrapper.Wrap静态方法来生成这些实例。转换是C规范中最毛糙的部分;当您开始使用它时,超出了扩展数字类型的自然情况,您很快就会遇到这样的模糊惊喜。
- 实际上,我是在尝试使用新的Microsoft.AspNetCore.Mvc.ActionResult时遇到这种情况的。
- 好吧,MVC团队有理由说ActionResult只是打算用作返回类型…但我仍然认为,依靠隐式转换来实现这一点是一个坏主意,而且要求人们写ActionResult.For(value)更好。但我不是团队中的设计师,甚至不是最终用户。
C语言规范清楚地说明了这一点:
第6.4.1节允许的用户定义转换
For a given source type S and target type T, if S or T are nullable
types, let S0 and T0 refer to their underlying types, otherwise S0 and
T0 are equal to S and T respectively. A class or struct is permitted
to declare a conversion from a source type S to a target type T only
if all of the following are true:
-
S0 and T0 are different types.
-
Either S0 or T0 is the class or struct type in which the operator declaration takes place.
-
Neither S0 nor T0 is an interface-type.
-
Excluding user-defined conversions, a conversion does not exist from S to T or from T to S.
你的案件违反了条件第3点。源类型是接口!
- 但在"编译器满意"的情况下,当T是接口(IEnumerable时,将调用隐式运算符。在我阅读本文时,我希望在这种情况下它也会失败(是的,您传递的不是接口,但在本例中为接口定义了隐式运算符)。
- 编译器不知道隐式转换声明中的T是什么。它只会在您尝试进行转换时推断T是一个接口时抱怨。没什么问题吧?@ Evk
- 当您执行Wrapper> wrapper2 = new[] { 1, 2, 3 };操作时,编译器使用带有签名implicit operator Wrapper>(IEnumerable value)的隐式运算符(因为这是赋值的左侧),据我所知,根据规范,这是非法的。
- EricLippert的回答指出,现实与用户定义的隐式转换规范不同:stackoverflow.com/a/39619053/5311735。也许这就是其中之一。
- @EVK只是一种推测,也许编译器看到了左边的部分并推断出T是IEnumerable,但随后试图从右边得到源类型s,发现它是int[]。哎呀!不是接口!"它认为,并通过将int[]插入参数来执行转换。
- 可能.我想这是在这里的某个地方记录的:github.com/dotnet/roslyn/blob/master/src/compilers/csharp/&hellip;,在"故意违反规范"一节下,但我不想尝试解密那里写的内容:)