编译器用显式转换为.NET类型将显式转换替换为我自己的类型?

Mar*_*der 11 c# generics casting explicit operators

我有以下代码:

public struct Num<T>
{
    private readonly T _Value;

    public Num(T value)
    {
        _Value = value;
    }

    static public explicit operator Num<T>(T value)
    {
        return new Num<T>(value);
    }
}

...
double d = 2.5;
Num<byte> b = (Num<byte>)d;
Run Code Online (Sandbox Code Playgroud)

这段代码编译,令我惊讶.显式转换应该只接受a byte而不是a double.但双重被接受了.当我在转换器中放置断点时,我发现它value已经是byte有价值的2.通过从double到byte的转换应该是显式的.

如果我用ILSpy反编译我的EXE,我会看到下一个代码:

double d = 2.5;
Program.Num<byte> b = (byte)d;
Run Code Online (Sandbox Code Playgroud)

我的问题是:来自哪里的额外演员byte?为什么那里有额外的演员?我的演员去Num<byte>哪儿了?

编辑
结构Num<T>是整个结构,所以没有更多隐藏的额外方法或运算符.


按要求编辑 IL:

IL_0000: nop
IL_0001: ldc.r8 2.5 // Load the double 2.5.
IL_000a: stloc.0
IL_000b: ldloc.0
IL_000c: conv.u1 // Once again the explicit cast to byte.
IL_000d: call valuetype GeneriCalculator.Program/Num`1<!0> valuetype GeneriCalculator.Program/Num`1<uint8>::op_Explicit(!0) 
IL_0012: stloc.1
IL_0013: ret
Run Code Online (Sandbox Code Playgroud)

Eri*_*ert 16

让我们退后一步,提出一些澄清问题:

这个程序合法吗?

public struct Num<T>
{
    private readonly T _Value;

    public Num(T value)
    {
        _Value = value;
    }

    static public explicit operator Num<T>(T value)
    {
        return new Num<T>(value);
    }
}

class Program
{
    static void Main()
    {
        double d = 2.5;
        Num<byte> b = (Num<byte>)d;
    }
}
Run Code Online (Sandbox Code Playgroud)

是.

你能解释为什么演员合法吗?

正如Ken Kin指出的那样,我在这里解释一下:

http://blogs.msdn.com/b/ericlippert/archive/2007/04/16/chained-user-defined-explicit-conversions-in-c.aspx

简而言之:用户定义的显式转换可能在"两端"插入内置显式转换.也就是说,我们可以插入从源表达式到用户定义的转换方法的参数类型的显式转换,或者从用户定义的转换方法的返回类型到转换的目标类型.(或者,在极少数情况下,两者都有.)

在这种情况下,我们在参数类型byte中插入一个内置的显式转换,因此您的程序与您编写的相同:

        Num<byte> b = (Num<byte>)(byte)d;
Run Code Online (Sandbox Code Playgroud)

这是理想的行为.double可以显式转换为byte,因此double也可以显式转换为Num<byte>.

有关完整说明,请阅读C#4规范中的第6.4.5节"用户定义的显式转换".

为什么IL生成调用op_Implicit而不是op_Explicit

它没有; 这个问题是以虚假为前提的.上述程序生成:

  IL_0000:  nop
  IL_0001:  ldc.r8     2.5
  IL_000a:  stloc.0
  IL_000b:  ldloc.0
  IL_000c:  conv.u1
  IL_000d:  call       valuetype Num`1<!0> valuetype Num`1<uint8>::op_Explicit(!0)
  IL_0012:  stloc.1
  IL_0013:  ret
Run Code Online (Sandbox Code Playgroud)

你可能正在查看你的程序的旧版本.做一个干净的重建.

是否还有其他情况下C#编译器以静默方式插入显式转换?

是; 事实上,这是今天第二次出现这个问题.看到

C#类型转换不一致?


Ken*_*Kin 10

首先,我们来看看Lippert先生的博客:

C#中用户定义的显式转换链接

编译器会有时候1插入为我们明确的转换:

  • 部分博文:

    ...

    当用户定义的显式转换需要在调用方或返回方进行显式转换时,编译器将根据需要插入显式转换.

    编译器认为,如果开发人员首先在代码中放置显式强制转换,那么开发人员就知道他们在做什么,并承担任何转换可能失败的风险.

    ...

作为这个问题,这只是有时候的一个时间.编译器插入的显式转换就像我们在下面的代码中编写的那样:

  • 使用显式转换测试通用方法

    public static class NumHelper {
        public static Num<T> From<T>(T value) {
            return new Num<T>(value);
        }
    }
    
    public partial class TestClass {
        public static void TestGenericMethodWithExplicitConversion() {
            double d=2.5;
            Num<byte> b=NumHelper.From((byte)d);
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    并且生成的测试方法的IL是:

    IL_0000: nop
    IL_0001: ldc.r8 2.5
    IL_000a: stloc.0
    IL_000b: ldloc.0
    IL_000c: conv.u1
    IL_000d: call valuetype Num`1<!!0> NumHelper::From<uint8>(!!0)
    IL_0012: stloc.1
    IL_0013: ret
    
    Run Code Online (Sandbox Code Playgroud)

让我们退后一步,看看显式运算符的测试作为你的问题:

  • 测试显式运算符

    public partial class TestClass {
        public static void TestExplicitOperator() {
            double d=2.5;
            Num<byte> b=(Num<byte>)d;
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    你之前已经看过IL:

    IL_0000: nop
    IL_0001: ldc.r8 2.5
    IL_000a: stloc.0
    IL_000b: ldloc.0
    IL_000c: conv.u1
    IL_000d: call valuetype Num`1<!0> valuetype Num`1<uint8>::op_Explicit(!0)
    IL_0012: stloc.1
    IL_0013: ret
    
    Run Code Online (Sandbox Code Playgroud)

你注意到它们非常相似吗?不同之处在于参数!0是原始代码的类型定义中的泛型参数,而!!0在泛型方法测试中,参数是方法定义中的泛型参数.您可能需要查看§II.7.1规范ECMA-335的章节.

但是,这里最重要的一点是它们都进入<uint8>了泛型定义的类型(字节); 正如我上面提到的,根据利珀特先生的博文告诉我们,编译器有时插入显式转换,当你做了指定他们明确!

最后,正如你认为这是编译器的奇怪行为,让我猜你可能会认为编译器该做什么:

  • 通过指定类型参数来测试泛型方法:

    public partial class TestClass {
        public static void TestGenericMethodBySpecifyingTypeParameter() {
            double d=2.5;
            Num<byte> b=NumHelper.From<byte>(d);
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

我猜对了吗?无论如何,我们在这里再次感兴趣的是IL.我迫不及待地想看到IL,它是:

0PC4l.png

Ooooops ..似乎不像编译器认为显式运算符的行为.

对于结论,当我们明确地指定转换时,我们期望将一个事物转换为另一个事物是非常语义的,编译器推导出并插入所涉及类型的明显必要的转换; 一旦发现所涉及的类型不合法转换,就会抱怨,就像我们指定一个更简单的错误转换,例如(String)3.1415926 ...

希望它现在更有帮助而不会失去正确性.

1:这是我个人的表达,有时候,在博文中实际上是根据需要说的.


以下是对比的一些测试,当一个人可能期望用现有的显式运算符转换类型时; 我在代码中添加了一些注释来描述每个案例:

double d=2.5;
Num<byte> b=(Num<byte>)d; // explicitly
byte x=(byte)d; // explicitly, as the case above

Num<byte> y=d; // no explicit, and won't compile

// d can be `IConvertible`, compiles
Num<IConvertible> c=(Num<IConvertible>)d;

// d can be `IConvertible`; 
// but the conversion operator is explicit, requires specified explicitly
Num<IConvertible> e=d;

// d cannot be `String`, won't compile even specified explicitly
Num<String> s=(Num<String>)d;

// as the case above, won't compile even specified explicitly
String t=(String)d; 
Run Code Online (Sandbox Code Playgroud)

也许它更容易理解.

  • 我不明白为什么这个答案被否决了.它是正确的. (7认同)