C# 'is' type check on struct - odd .NET 4.0 x86 optimization behavior

前端 未结 6 936
孤城傲影
孤城傲影 2021-02-02 07:20

Update: I have filed a bug report with Microsoft Connect, please vote for it!

Update 2: Microsoft have marked the bug report as fixed

相关标签:
6条回答
  • 2021-02-02 07:31

    That's not cool. I think you should file a bug. (connect.microsoft.com)

    Also, this seems to work (I only tested for your fail case though):

        public static bool IsGuid(object item)
        {
            return item.GetType() == typeof(Guid);
        }
    
    0 讨论(0)
  • 2021-02-02 07:35

    Interestingly enough this works correctly:

    using System;
    public class Program
    {
        public static bool IsGuid(object item)
        {
            return item is Guid;
        }
    
        public static void Main()
        {
            Guid s = Guid.NewGuid();
            Console.Write(IsGuid(s));
        }
    }
    

    Here is the difference in the il:

    .method public hidebysig static void  Main() cil managed
    {
      .entrypoint
      // Code size       23 (0x17)
      .maxstack  1
      .locals init (valuetype [mscorlib]System.Guid V_0)
      IL_0000:  call       valuetype [mscorlib]System.Guid [mscorlib]System.Guid::NewGuid()
      IL_0005:  stloc.0
      IL_0006:  ldloc.0
      IL_0007:  box        [mscorlib]System.Guid
      IL_000c:  call       bool Program::IsGuid(object)
      IL_0011:  call       void [mscorlib]System.Console::Write(bool)
      IL_0016:  ret
    } // end of method Program::Main
    
    0 讨论(0)
  • 2021-02-02 07:45

    Other than a few nops, reflector indictes the only differences are in the IsGuid method:

    DEBUG:

    .method public hidebysig static bool IsGuid(object item) cil managed
    {
        .maxstack 2
        .locals init (
            [0] bool CS$1$0000)
        L_0000: nop 
        L_0001: ldarg.0 
        L_0002: isinst [mscorlib]System.Guid
        L_0007: ldnull 
        L_0008: cgt.un 
        L_000a: stloc.0 
        L_000b: br.s L_000d
        L_000d: ldloc.0 
        L_000e: ret 
    }
    

    RELEASE:

    .method public hidebysig static bool IsGuid(object item) cil managed
    {
        .maxstack 8
        L_0000: ldarg.0 
        L_0001: isinst [mscorlib]System.Guid
        L_0006: ldnull 
        L_0007: cgt.un 
        L_0009: ret 
    }
    

    I'm not fluent enough to explain these differences but this is a community wiki answer so maybe somebody else can enlighten us?

    Changing the method to make it generic works around this (possible bug?)

    public static bool IsGuid<T>(T item)
    {
        return item is Guid;
    }
    

    As does forcing it into a local varible (but it must be used in the method to prevent the optimizations kicking in):

    public static bool IsGuid(object item)
    {
        bool a = item is Guid;
        a.ToString();
        return a;
    }
    
    0 讨论(0)
  • 2021-02-02 07:49

    I worked up a similar example that fails the same way:

    using System;
    using System.Runtime.CompilerServices;
    
    public class Program {
      static void Main() {
        Console.Write(Verify(Test.Create()));
        Console.ReadLine();
      }
      //[MethodImpl(MethodImplOptions.NoInlining)]
      static bool Verify(IDisposable item) {
        return item is Test;
      }
      struct Test : IDisposable {
        public void Dispose() { }
        public static Test Create() { return new Test(); }
      }
    }
    

    It is a JIT optimizer bug. Can't quite put the finger on it, it optimizes the code heavily. But it looks to me like it gets in trouble when it optimizes the boxing conversion away. Pretty serious bug, frankly.


    This bug has been fixed, I can no longer repro it. My current version of clrjit.dll is 4.0.30319.237 dated May 17th 2011. I can't tell exactly what update repaired it. I got a security update on Aug 5th 2011 that updated clrjit.dll to revision 235 with a date of Apr 12, that would be the earliest.

    0 讨论(0)
  • 2021-02-02 07:55

    To answer your last question you can add the MethodImpl attribute with the option MethodImplOptions.NoInlining to your IsGuid method as an workaround to fix the problem.

    I just made a simple test of switching between Debug and Release configuration for x86 on the .NET 4.0 and this seems to resolve the problem. I haven't yet run your runtests.bat though.

    You should also submit an issue in Connect if one is not yet submitted and link it from your question.

    0 讨论(0)
  • 2021-02-02 07:58

    Here's my results on XP SP3 (x86):

    >runtest
    Compiler: Framework\v4.0.30319\csc.exe
    False => x86
    False => x86 (Optimized)
    True => x86 (Debug)
    False => x86 (Debug + Optimized)
    False => AnyCPU
    False => AnyCPU (Optimized)
    True => AnyCPU (Debug)
    False => AnyCPU (Debug + Optimized)
    
    >runtest v3.5
    Compiler: Framework\v3.5\csc.exe
    True => x86
    True => x86 (Optimized)
    True => x86 (Debug)
    True => x86 (Debug + Optimized)
    True => AnyCPU
    True => AnyCPU (Optimized)
    True => AnyCPU (Debug)
    True => AnyCPU (Debug + Optimized)
    

    The interesting point about this result is that the .NET 4 target fails on AnyCPU (running as x86) but works on AnyCPU (running as x64).

    0 讨论(0)
提交回复
热议问题