CS0184 should be a compile error

C# "is" operator may return inconsistet result with CLR specifications. So I think warning CS0184 should be a compile error.


CLR allows to cast a DayOfWeek[] object to int[] because the undertlying type of DayOfWeek is Int32.  

2 4.3 castclass – cast an object to a class

Description:

The castclass instruction attempts to cast obj (an O) to the class. Class is a metadata token (a typeref or typedef), indicating the desired class. If the class of the object on the top of the stack does not implement class (if class is an interface), and is not a subclass of class (if class is a regular class), then an InvalidCastException is thrown.

Note that:
  1. Arrays inherit from System.Array
  2. If Foo can be cast to Bar, then Foo[] can be cast to Bar[]
  3. For the purposes of 2., enums are treated as their undertlying type: thus E1[] can cast to E2[] if E1 and E2 share an underlying type If obj is null, castclass succeeds and returns null. This behavior differs from isInst.
CLI Specifications Partition III

Remember this rule and see this C# code.



static void Main(string[] args)
{
    DayOfWeek[] enumArray = new DayOfWeek[] {
        DayOfWeek.Monday,
        DayOfWeek.Tuesday,
        DayOfWeek.Wednesday
    };

    int[] array1 = (int[])((object)enumArray);

    foreach (int i in array1)
    {
        Console.Write("{0} ", i);
    }

    Console.WriteLine();

    Console.WriteLine("[CLR] Is int[] assignable from DayOfWeek[] : {0}",
        typeof(int[]).IsAssignableFrom(typeof(DayOfWeek[])) ) ;

    // Warning CS0184
    // http://msdn2.microsoft.com/en-us/library/230kb9yt.aspx
    Console.WriteLine("[C#] enumArray is int[] : {0}", enumArray is int[]);

    Console.WriteLine("[C#] (object)enumArray is int[] : {0}", (object)enumArray is int[] );
}

 

and the result:



foreach Console.Write : 1 2 3
[CLR] Is int[] assignable from DayOfWeek[] : True
[C#] enumArray is int[] : False
[C#] (object)enumArray is int[] : True

 

C# Compiler generates a waring CS0184 and returns false for "enumArray is int[]".

In the MSDN, there is an explanation about CS0184.

The expression can never be true because the variable you are testing is neither declared as type nor derived from type.

But Unfortunately CLR allows such a cast.
I think It is better to change CS0184 to a compile error rather than it always returns false.

What do you think



Answer this question

CS0184 should be a compile error

  • kavithasiva

    Well, there are a number of places where there's a slight impedence mismatch between what the CLR says and what the C# language defines.

    Since the language doesn't prevent you from writing it, I think it's right that it's a warning. Errors are about code that isn't legal, not code that is legal but perhaps generates a nonsensical result.


  • anemvr

    It looks to me like the C# language specification is effectively in error here. (There are a few places like this - including a similar one when it comes to unboxing and enums.)

    Rather than changing the warning to an error, I think it would be better for the language specification to take the conversion of enum arrays into account.

    Jon



  • pato135

    Jon,
    I have read your interesting article about unboxing and enums. Thanks.

    I have no idia whether C# spec should use the same rules as the CLI spec or not, but I agree that there are some impedance mismatches left in C# spec and CLI spec.
    I think we should also take it into account that "as" operator may behave in a different way from "is" operators in this case and this mismatches also should be removed.

    Anyway, I introduce some other mismatches just for your information.

    case 1: another type of enum underlying type covariance mismatch.

    C# compiler behaves as if an instance of delegate "int[] Bar()" cannot be constructed form a function "DayOfWeek[] Func()", but CLR allows it.



    using System;
    static class Test
    {
      public delegate DayOfWeek[] Foo();
      public delegate int[] Bar();
      public static DayOfWeek[] Func()
      {
        return new DayOfWeek[] { DayOfWeek.Monday };
      }
      static void Main(string[] args)
      {
        Foo foo1 = Func;
        Bar bar1 = Func; // Compile Error

        // CLR Supports this
        Bar bar2 = (Bar)Delegate.CreateDelegate(
          typeof(Bar), typeof(Test).GetMethod("Func"));

        // is operator always returns false
        Console.WriteLine(
           "Func is Foo : {0}",
            Func is Foo);
        // is operator always returns false
        Console.WriteLine(
          "Func is Bar : {0}",
           Func is Bar);

        Console.WriteLine(bar2()[0]);
      }
    }

     

    Note that call of "is" operator to compare a method with delegate returns no useful information.

    case 2: Generic delegates covariance/contravariance.

    CLR has some covariant/contravariant features with generic interfaces and generic delegates.
    http://blogs.msdn.com/rmbyers/archive/2005/02/16/375079.aspx

    Because C# generics don't allow to describe such interfaces and delegates, we have to use ilasm with MSIL to create "interface IComparer<-T>".
    Anyway, we can use the generated generic type in C#.
    In this case C# compiler behaves as if there is no such compatibility unlike in the case of array covariance.
    Note that there are some difference between generic delegates covariance/contravariance and generic interfaces covariance/contravariance when you use "is" operator.

    I made some interfaces and delegates for this test.

    • Variant.Function<+A>
    • Variant.Function<-A, +B>
    • Variant.Function<-A, -B, +C>
    • Variant.IComparer<-T>
    • Variant.ICloneable<+T>

    You can download the compiled assembly from here.
    http://www.dwahan.net/nyaruru/hatena/variant.zip

    And this is a sample code.



    using System;
    using System.IO;
    static class Program
    {
      static void Main(string[] args)
      {
        Variant.Function<string> func1
          = new Variant.Function<string>(Test1);
        Console.WriteLine(
          "func1 is Variant.Function<object> : {0}",
           func1 is Variant.Function<object>);
        Console.WriteLine(
          "(object)func1 is Variant.Function<object> : {0}",
           (object)func1 is Variant.Function<object>);

        Variant.Function<Stream, Stream> func2
          = new Variant.Function<Stream, Stream>(Test2);

        Console.WriteLine(
          "func2 is Variant.Function<MemoryStream, Stream> : {0}",
           func2 is Variant.Function<MemoryStream, Stream>);
        Console.WriteLine(
          "(object)func2 is Variant.Function<MemoryStream, Stream> : {0}",
           (object)func2 is Variant.Function<MemoryStream, Stream>);

        Variant.IComparer<object> test3 = new Test3();
        Console.WriteLine(
          "test3 is Variant.IComparer<string> : {0}",
           test3 is Variant.IComparer<string>);
        Console.WriteLine(
          "(object)test3 is Variant.IComparer<string> : {0}",
           (object)test3 is Variant.IComparer<string>);

        Variant.ICloneable<Test4> test4 = new Test4();
        Console.WriteLine(
          "test4 is Variant.ICloneable<Stream> : {0}",
           test4 is Variant.ICloneable<Stream>);
        Console.WriteLine(
          "(object)test4 is Variant.ICloneable<Stream> : {0}",
           (object)test4 is Variant.ICloneable<Stream>);
      }
      public static string Test1()
      {
        return "Monday";
      }
      public static Stream Test2(Stream a)
      {
        return a;
      }
      public class Test3 : Variant.IComparer<object>
      {
        public bool Compare(object x, object y)
        {
          return Object.Equals(x, y);
        }
      }
      public class Test4 : MemoryStream, Variant.ICloneable<Test4>
      {
        public Test4 Clone()
        {
          return new Test4();
        }
        object ICloneable.Clone()
        {
          return new Test4();
        }
      }
    }

     

    the result is:



    func1 is Variant.Function<object> : False
    (object)func1 is Variant.Function<object> : True
    func2 is Variant.Function<MemoryStream, Stream> : False
    (object)func2 is Variant.Function<MemoryStream, Stream> : True
    test3 is Variant.IComparer<string> : True
    (object)test3 is Variant.IComparer<string> : True
    test4 is Variant.ICloneable<Stream> : True
    (object)test4 is Variant.ICloneable<Stream> : True

     

    Note that call of "is" operator to test generic delegates covariance/contravariance returns inconsistent result with CLR spec and the CS0184, but in the case of generic interfaces it returns consistent result and no CS0184. 


  • Golden Teapot

    The compiler's warning is factually incorrect though - as demonstrated by the fact that when you cast it to an object, it works fine. I agree that it shouldn't be an error - I don't think it should be a warning either though; I think the C# spec should use the same rules as the CLI spec.

    Furthermore, I don't think the fact that there's an impedence mismatch should be left as it is. Currently, there are things which "work" at runtime where the specification says they shouldn't. According to the specification, a cast from a reference which is actually an instance of DaysOfTheWeek[] to int[] should throw InvalidCastException. It doesn't.

    I understand it's difficult to get rid of these impedence mismatches, but work should be done towards that end.

    Jon


  • Milen

    Excellent - I'll have a close look at that when I get a chance.

    Jon


  • CS0184 should be a compile error