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

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

Dim zipEntry1 As ZipEntry = zipFile1.Entries(0)

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

ZipEntry zipEntry1 = zipFile1.Entries[0];

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

невозможно применить индексирование с [] к выражению типа - Система.Коллекции.Родовой.Интерфейс ICollection'

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

Я протестировали ниже VB.Net и он строит 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

это не построить:

using Ionic.Zip;

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

почему это произошло, и есть ли способ обойти это?

3 ответов


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

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

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 себя, если вы довольны тем, что делает.


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

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

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


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