Search code examples
c#vb.neticollection

Why can I apply an indexer to an ICollection in VB.Net, but not in C#


Was converting some code from VB.Net to C#, when I came across this, in some code using the Ionic Zip library:

Dim zipEntry1 As ZipEntry = zipFile1.Entries(0)

Simple enough:

ZipEntry zipEntry1 = zipFile1.Entries[0];

I get this error on C#:

Cannot apply indexing with [] to an expression of type 'System.Collections.Generic.ICollection'

Both are using the same version of the DLL, on both zipFile1.Entries is a generic ICollection.

I have tested the below on VB.Net, and it builds successfullly:

Option Strict On
Option Explicit On

Imports Ionic.Zip

Module Module1

    Sub Main()

        Dim zipFile1 = ZipFile.Read("C:\test")
        Dim zipEntry = zipFile1.Entries(0)

    End Sub

End Module

This does not build:

using Ionic.Zip;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            var zipFile1 = ZipFile.Read(@"C:\test");
            var zipEntry = zipFile1.Entries[0];
        }
    }
}

Why does this happen, and is there a way around it?


Solution

  • Bizarrely enough, it looks like VB has special support for IEnumerable<T> and implicitly provides an indexer which actually calls Enumerable.ElementAtOrDefault. ICollection<T> extends IEnumerable<T>, so the same facility exists there. ICollection<T> doesn't provide a "real" indexer, hence the problem when you try using it from C#.

    Sample program:

    Option Strict On
    
    Public Class Test
        Public Shared Sub Main(args As String())
          Dim x as System.Collections.Generic.ICollection(Of String) = args
          Console.WriteLine(x(0))
        End Sub
    End Class
    

    Generated IL for Main:

    .method public static void  Main(string[] args) cil managed
    {
      .entrypoint
      .custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 ) 
      // Code size       15 (0xf)
      .maxstack  2
      .locals init 
          (class [mscorlib]System.Collections.Generic.IEnumerable`1<string> V_0)
      IL_0000:  ldarg.0
      IL_0001:  stloc.0
      IL_0002:  ldloc.0
      IL_0003:  ldc.i4.0
      IL_0004:  call       !!0
         [System.Core]System.Linq.Enumerable::ElementAtOrDefault<string>(
            class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>,
            int32)
      IL_0009:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_000e:  ret
    } // end of method Test::Main
    

    I find it very odd that VB provides this implicitly - it's really dangerous to make it look like it's fine to index into a collection which doesn't necessarily supply an efficient indexing operation.

    Of course, you can call ElementAtOrDefault yourself, if you're happy with what that does.