Почему я могу применить индексатор к ICollection в VB.Net, но не в С#

Преобразовал какой-то код из VB.Net в С#, когда я столкнулся с этим, в некотором коде с использованием библиотеки Ionic Zip:

Dim zipEntry1 As ZipEntry = zipFile1.Entries(0)

Достаточно просто:

ZipEntry zipEntry1 = zipFile1.Entries[0];

Я получаю эту ошибку на С#:

Невозможно применить индексирование с [] к выражению типа 'System.Collections.Generic.ICollection'

Оба используют одну и ту же версию DLL, на обоих zipFile1.Entries есть общий ICollection.

Я тестировал ниже на VB.Net, и он успешно работает:

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

Это не строится:

using Ionic.Zip;

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

Почему это происходит, и есть ли способ вокруг него?

Ответ 1

Необычно, похоже, что у VB есть специальная поддержка IEnumerable<T> и неявно предоставляет индекс, который фактически вызывает Enumerable.ElementAtOrDefault. ICollection<T> extends IEnumerable<T>, поэтому там существует одно и то же средство. ICollection<T> не предоставляет "реального" индексатора, поэтому проблема при попытке использовать его с С#.

Пример программы:

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

Сгенерированный IL для 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

Мне очень странно, что VB предоставляет это неявно - действительно опасно сделать так, чтобы он выглядел так, чтобы индексировать в коллекцию, которая не обязательно обеспечивает эффективную операцию индексирования.

Конечно, вы можете вызвать ElementAtOrDefault самостоятельно, если вы довольны тем, что это делает.

Ответ 2

Строго просматривается, ICollection<T> - это интерфейс к неупорядоченному набору элементов (точнее, коллекция, элементы которой по отдельности не могут быть доступны по их индексу). Это по определению.

Но вы все равно можете использовать метод расширения LINQ ElementAt(int index). Это будет просто перебирать все элементы index раз каждый раз, когда вы его вызываете (поэтому он обычно медленнее).

ПРИМЕЧАНИЕ: ICollection<T> не следует путать с Collection<T>. Последний реализует IList<T> (между прочим), который по определению указывает, что к каждому элементу можно получить доступ по его индексу.

Ответ 3

У VB уже давно была идея члена по умолчанию для него классов, который для коллекций всегда является элементом Item().