14

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?

JMK
  • 27,273
  • 52
  • 163
  • 280

4 Answers4

19

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.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • I guess the idea that you *can't* do it in C# is more of a leftover from C, where you'd assert that the `[]` operator can efficiently index a sequence using the x86's addressing modes. Whereas in VB, you say that you want the 3rd element, and you get the 3rd element. – dialer Apr 07 '13 at 17:05
  • 1
    @dialer: Well the provision of an indexer doesn't *have* to be terribly efficient - but I don't like the idea of it being implicit, with no indication to the caller that this isn't being provided by the declaring class. There are times where it could be *dramatically* worse - imagine if you used a for loop over the index instead of a foreach loop, for something which had to read a text file each time to get to the nth line (or whatever). – Jon Skeet Apr 07 '13 at 17:08
  • This will get very weird if you access an item by index on a `Set` for example. – Magnus Mar 25 '19 at 10:53
7

Strictly viewed, ICollection<T> is an interface to an unordered collection of elements (more precisely, a collection whose elements cannot individually be accessed by their index). That is just by definition.

But you can still use LINQ's ElementAt(int index) extension method. That would just iterate through all elements index times every time you call it (so it is generally slower).

NOTE: ICollection<T> is not to be confused with Collection<T>. The latter implements IList<T> (among other things), which by definition does specify that each element can be accessed by its index.

dialer
  • 4,348
  • 6
  • 33
  • 56
0

VB has long had the idea of a default member for it's classes, which for collections is always the member Item().

Pieter Geerkens
  • 11,775
  • 2
  • 32
  • 52
  • This nearly always just corresponds to the indexer for the class, which is not at all a VB-specific thing. The VB shortcut demonstrated in this thread for 'ElementAtOrDefault' is the weird VB-specific thing. – Dave Doknjas Apr 07 '13 at 23:37
  • Except in this case it's not Item but ElementAt – Carl Onager Mar 11 '15 at 15:13
0

zipFile1.Entries(0) invokes the so-called Default Query Indexer, which is a little-known feature defined in the VB language specification:

Default Query Indexer

Every queryable collection type whose element type is T and does not already have a default property is considered to have a default property of the following general form:

Public ReadOnly Default Property Item(index As Integer) As T
    Get
        Return Me.ElementAtOrDefault(index)
    End Get
End Property
Heinzi
  • 167,459
  • 57
  • 363
  • 519