wake-up-neo.com

IList <T> und IReadOnlyList <T>

Wenn ich eine Methode habe, die einen Parameter benötigt,

  • Verfügt über eine Count-Eigenschaft
  • Hat einen Integer-Indexer (Get-Only)

Was soll der Typ dieses Parameters sein? Ich würde IList<T> vor .NET 4.5 wählen, da dafür keine andere indizierbare Erfassungsschnittstelle vorhanden war und Arrays dies implementieren, was ein großer Vorteil ist.

.NET 4.5 führt jedoch die neue IReadOnlyList<T>-Schnittstelle ein, und ich möchte, dass meine Methode auch dies unterstützt. Wie kann ich diese Methode schreiben, um sowohl IList<T> als auch IReadOnlyList<T> zu unterstützen, ohne die Grundprinzipien wie DRY zu verletzen?

Edit : Daniels Antwort brachte mir einige Ideen:

public void Foo<T>(IList<T> list)
    => Foo(list, list.Count, (c, i) => c[i]);

public void Foo<T>(IReadOnlyList<T> list)
    => Foo(list, list.Count, (c, i) => c[i]);

private void Foo<TList, TItem>(
    TList list, int count, Func<TList, int, TItem> indexer)
    where TList : IEnumerable<TItem>
{
    // Stuff
}

Edit 2: Oder ich könnte einfach einen IReadOnlyList<T> akzeptieren und einen Helfer wie diesen bereitstellen:

public static class CollectionEx
{
    public static IReadOnlyList<T> AsReadOnly<T>(this IList<T> list)
    {
        if (list == null)
            throw new ArgumentNullException(nameof(list));

        return list as IReadOnlyList<T> ?? new ReadOnlyWrapper<T>(list);
    }

    private sealed class ReadOnlyWrapper<T> : IReadOnlyList<T>
    {
        private readonly IList<T> _list;

        public ReadOnlyWrapper(IList<T> list) => _list = list;

        public int Count => _list.Count;

        public T this[int index] => _list[index];

        public IEnumerator<T> GetEnumerator() => _list.GetEnumerator();

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }
}

Dann könnte ich es als Foo(list.AsReadOnly()) bezeichnen


Edit 3: Arrays implementieren sowohl IList<T> als auch IReadOnlyList<T>, ebenso die List<T>-Klasse. Dies macht es ziemlich selten, eine Klasse zu finden, die IList<T> aber nicht IReadOnlyList<T> implementiert.

31
Şafak Gür

Sie haben hier kein Glück. IList<T> implementiert IReadOnlyList<T> nicht. List<T> implementiert beide Schnittstellen, aber ich denke, das ist nicht das, was Sie wollen.

Sie können jedoch LINQ verwenden: 

  • Die Erweiterungsmethode Count() überprüft intern, ob es sich bei der Instanz tatsächlich um eine Objektgruppe handelt, und verwendet dann die Eigenschaft Count.
  • Die Erweiterungsmethode ElementAt() prüft intern, ob die Instanz tatsächlich eine Liste ist, und verwendet dann den Indexer.
20
Daniel Hilgarth

Wenn Sie mehr daran interessiert sind, das Prinzip von DRY über der Leistung zu erhalten, können Sie ()dynamic wie folgt verwenden:

public void Do<T>(IList<T> collection)
{
    DoInternal(collection, collection.Count, i => collection[i]);
}
public void Do<T>(IReadOnlyList<T> collection)
{
    DoInternal(collection, collection.Count, i => collection[i]);
}

private void DoInternal(dynamic collection, int count, Func<int, T> indexer)
{
    // Get the count.
    int count = collection.Count;
}

Ich kann jedoch nicht in gutem Glauben sagen, dass ich das empfehlen würde, da die Fallstricke zu groß sind:

  • Jeder Aufruf von collection in DoInternal wird zur Laufzeit aufgelöst. Sie verlieren die Typensicherheit, Überprüfungen der Kompilierzeit usw.
  • Leistungsabfall (wenn auch nicht schwerwiegend für den Einzelfall, kann aber aggregiert sein) will

Ihr Helfer-Vorschlag ist am nützlichsten, aber ich denke, Sie sollten ihn umdrehen; Da die IReadOnlyList<T>-Schnittstelle in .NET 4.5 eingeführt wurde, unterstützen viele APIs diese nicht, unterstützen jedoch die IList<T>-Schnittstelle .

Sie sollten jedoch einen AsList-Wrapper erstellen, der einen IReadOnlyList<T> nimmt und einen Wrapper in einer IList<T>-Implementierung zurückgibt.

Wenn Sie jedoch auf Ihrer API hervorheben möchten, dass Sie einen IReadOnlyList<T> verwenden (um die Tatsache zu verdeutlichen, dass Sie die Daten nicht mutieren), ist die Erweiterung AsReadOnlyList, die Sie jetzt haben, angemessener, aber ich würde die folgende Optimierung auf AsReadOnly:

public static IReadOnlyList<T> AsReadOnly<T>(this IList<T> collection)
{
    if (collection == null)
        throw new ArgumentNullException("collection");

    // Type-sniff, no need to create a wrapper when collection
    // is an IReadOnlyList<T> *already*.
    IReadOnlyList<T> list = collection as IReadOnlyList<T>;

    // If not null, return that.
    if (list != null) return list;

    // Wrap.
    return new ReadOnlyWrapper<T>(collection);
}
4
casperOne

Da IList<T> und IReadOnlyList<T> keinen nützlichen "Vorfahren" gemeinsam haben, und wenn Sie nicht möchten, dass Ihre Methode einen anderen Parametertyp akzeptiert, können Sie nur zwei Überladungen bereitstellen.

Wenn Sie der Meinung sind, dass die Wiederverwendung von Codes oberste Priorität hat, können diese Überladungen den Aufruf an eine private-Methode weiterleiten, die IEnumerable<T> akzeptiert und LINQ in der von Daniel vorgeschlagenen Weise verwendet.

IMHO ist es jedoch wahrscheinlich besser, den Code nur einmal zu kopieren/einzufügen und zwei unabhängige Überladungen beizubehalten, die sich nur in der Art des Arguments unterscheiden. Ich glaube nicht, dass die Mikroarchitektur dieser Größenordnung etwas Greifbares bietet, und andererseits erfordert sie nicht offensichtliche Manöver und ist langsamer.

1
Jon

Was Sie brauchen, ist das in .Net 4.5 verfügbare IReadOnlyCollection<T> , bei dem es sich im Wesentlichen um einen IEnumerable<T> handelt, der Count als Eigenschaft hat. Wenn Sie jedoch auch eine Indizierung benötigen, benötigen Sie IReadOnlyList<T>, die auch einen Indexer enthält.

Ich weiß nicht, wie es Ihnen geht, aber ich denke, dieses Interface ist ein Muss, das schon lange nicht mehr existiert.

0
MaYaN