Why is the following Code Compiling and Executing successfully?
Asked Answered
I

1

6

I compiled the following code in .Net 3.5, Visual Studio 2012.

I expected to get an error on the line when the array gets assigned to my IReadOnlyCollection, because there is no implicit conversion defined from Array to my Interface. It compiles successful and does also not create any runtime errors.

Notes to be aware of:

  • There is no other IReadonlyCollection referenced. So it has to use mine (IReadonlyCollection was added to .Net4.5 and does not exist in earlier versions)
  • When i rename it to IMyCollection it does not compile anymore
  • When i change the namespace it does not compile anymore.

File1.cs:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace System.Collections.Generic
{
    public interface IReadOnlyCollection<T> : IEnumerable<T>, IEnumerable
    {
        int Count
        {
            get;
        }
    }
}

File2.cs:

using System.Collections.Generic;

namespace ConsoleApplication1
{
    public class Test
    {
        public Test()
        { }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Test[] foo = { new Test(), new Test(), new Test() };


            IReadOnlyCollection<Test> bar = foo;

            int count = bar.Count;
        }
    }
}

This is the IL code by the way:

   .method private hidebysig static void Main (
            string[] args
        ) cil managed 
    {
        .entrypoint
        .locals init (
            [0] class ConsoleApplication1.Test[] foo,
            [1] class System.Collections.Generic.IReadOnlyCollection`1<class ConsoleApplication1.Test> bar,
            [2] int32 count,
            [3] class ConsoleApplication1.Test[] CS$0$0000
        )

        IL_0000: nop
        IL_0001: ldc.i4.3
        IL_0002: newarr ConsoleApplication1.Test
        IL_0007: stloc.3
        IL_0008: ldloc.3
        IL_0009: ldc.i4.0
        IL_000a: newobj instance void ConsoleApplication1.Test::.ctor()
        IL_000f: stelem.ref
        IL_0010: ldloc.3
        IL_0011: ldc.i4.1
        IL_0012: newobj instance void ConsoleApplication1.Test::.ctor()
        IL_0017: stelem.ref
        IL_0018: ldloc.3
        IL_0019: ldc.i4.2
        IL_001a: newobj instance void ConsoleApplication1.Test::.ctor()
        IL_001f: stelem.ref
        IL_0020: ldloc.3
        IL_0021: stloc.0
        IL_0022: ldloc.0
        IL_0023: stloc.1
        IL_0024: ldloc.1
        IL_0025: callvirt instance int32 class System.Collections.Generic.IReadOnlyCollection`1<class ConsoleApplication1.Test>::get_Count()
        IL_002a: stloc.2
        IL_002b: ret
    }
Incorrect answered 13/3, 2014 at 12:5 Comment(6)
Probably you have some new compiler which knows how to work with array as a readonly collection and it generates some additional code to convert it. You can check it by verifying IL of your compiled class with ildasm utilLadle
Can you double check which platform you're actually targeting? I can't reproduce this. (VS2010, Fx 3.5)Guilty
@HenkHolterman VisualStudio 2012. I have choosen a .Net3.5 Console ApplicationIncorrect
Also as an idea, can you add Console.WriteLine(bar.GetType()) at the end? I'm not sure it will output System.ArrayLadle
@OleksandrPshenychnyy ConsoleApplication1.Test[] as expectedIncorrect
@Incorrect As is covered in ken2k's answer, it looks like you just stumbled upon the secret compiler phrase that lets you do this sort of thing before language / tool support catches up :-) Jon Skeet did it with async/await.Matchwood
F
2

All I'm saying is pure guess, but it doesn't fit as a comment so I'm posting it as an answer anyway:

  • I can reproduce your issue with VS 2012 and .Net 3.5.
  • I can't reproduce it with VS 2010, .Net 3.5 and identical code.

So the difference is really the compiler version.

As the name of the class and the namespace are important, I'm assuming it's a hardcoded rule that has been introduced in the compiler of VS 2012+, to support implicit conversion of the new types/interfaces introduced with .Net 4.5.

So I'm guessing it's another black magic trick with arrays. See for example this Hans Passant answer:

Both the compiler and the CLR have special knowledge of array types, just as they do of value types. The compiler sees your attempt at casting to IList<> and says "okay, I know how to do that!".

Fardel answered 13/3, 2014 at 13:28 Comment(1)
This is likely the same situation that allows you to implement async/await manually using the compiler-expected method names etc. There is another example that I can't remember, Jon Skeet does this sort of thing as a learning exercise and blogs about it.Matchwood

© 2022 - 2024 — McMap. All rights reserved.