给出以下结构:
public struct Foo<T>
{
public Foo(T obj) { }
public static implicit operator Foo<T>(T input)
{
return new Foo<T>(input);
}
}
此代码编译:
private Foo<ICloneable> MakeFoo()
{
string c = "hello";
return c; // Success: string is ICloneable, ICloneable implicitly converted to Foo<ICloneable>
}
但是这段代码没有编译 - 为什么?
private Foo<ICloneable> MakeFoo()
{
ICloneable c = "hello";
return c; // Error: ICloneable can't be converted to Foo<ICloneable>. WTH?
}
答案 0 :(得分:30)
显然,当其中一种类型是接口时,隐式用户定义的转换不起作用。来自C#规范:
6.4.1允许的用户定义转换
C#仅允许声明某些用户定义的转换。特别是,无法重新定义已存在的隐式或显式转换。 对于给定的源类型S和目标类型T,如果S或T是可空类型,则让S0和T0引用它们的基础类型,否则S0和T0分别等于S和T.只有满足以下所有条件时,才允许类或结构声明从源类型S到目标类型T的转换:
在第一种方法中,两种类型都不是接口类型,因此用户定义的隐式转换可以正常工作。
规范不是很清楚,但在我看来,如果涉及的类型之一是接口类型,编译器甚至不会尝试查找任何用户定义的隐式转换。
答案 1 :(得分:24)
(跟随接受的答案的评论。)
是的,这是规范的一个非常非常令人困惑的部分。关于“包含类型”的整个过程尤其存在严重缺陷。几年来我一直在努力寻找时间将整个部分完全重写为更连贯的内容,但它从未有过足够的优先级。
基本上我们在这里得到的是一个矛盾;我们说没有涉及接口的用户定义的隐式转换,但在这种情况下显然不是这样;有一个用户定义的从IC到Foo<IC>
的隐式转换,证明字符串通过转换进入Foo<IC>
。
我们真正应该更好地强调的是你引用的这一行:
特别是,不可能 重新定义已存在的隐含 或显式转换。
这就是推动整个事情的动力;当你实际上调用用户定义的方法时,不希望你曾经认为你正在进行表示保留类型测试。考虑一下这种变化:
interface IBar {}
interface IFoo : IBar {}
class Foo<T> : IFoo
{
public static explicit operator Foo<T>(T input) { whatever }
}
class Blah : Foo<IBar> {}
...
IBar bar = new Blah();
Foo<IBar> foo = (Foo<IBar>)bar;
现在,是否会调用用户定义的显式转换?该对象实际上是从Foo派生的,所以你希望它不会;这应该是一个简单的类型测试和引用赋值,而不是对辅助方法的调用。 接口值的强制转换始终被视为类型测试,因为对象几乎总是可能属于该类型,并且确实实现了该接口。我们不想否认你做一个廉价的代表保留转换的可能性。