AddRange zu einer Sammlung

108

Ein Mitarbeiter hat mich heute gefragt, wie ich einer Sammlung einen Bereich hinzufügen kann. Er hat eine Klasse, die von erbt Collection<T>. Es gibt eine Get-Only-Eigenschaft dieses Typs, die bereits einige Elemente enthält. Er möchte die Objekte in einer anderen Sammlung zur Eigenschaftssammlung hinzufügen. Wie kann er das C # 3-freundlich machen? (Beachten Sie die Einschränkung bezüglich der get-only-Eigenschaft, die Lösungen wie Union und Neuzuweisung verhindert.)

Sicher, ein Foreach mit Eigentum. Hinzufügen wird funktionieren. Ein List<T>AddRange im Stil wäre jedoch weitaus eleganter.

Es ist einfach genug, eine Erweiterungsmethode zu schreiben:

public static class CollectionHelpers
{
    public static void AddRange<T>(this ICollection<T> destination,
                                   IEnumerable<T> source)
    {
        foreach (T item in source)
        {
            destination.Add(item);
        }
    }
}

Aber ich habe das Gefühl, das Rad neu zu erfinden. Ich habe nichts Ähnliches in System.Linqoder Morelinq gefunden .

Schlechtes Design? Einfach Add anrufen? Vermissen Sie das Offensichtliche?

TrueWill
quelle
5
Denken Sie daran, dass das Q von LINQ "Abfrage" ist und sich wirklich mit dem Abrufen, Projektieren, Transformieren usw. von Daten befasst. Das Ändern vorhandener Sammlungen fällt nicht in den Bereich des beabsichtigten Zwecks von LINQ, weshalb LINQ nichts Außergewöhnliches bietet. of-the-Box dafür. Aber Erweiterungsmethoden (und insbesondere Ihre Stichprobe) wären dafür ideal.
Levi
Ein Problem ICollection<T>scheint keine AddMethode zu haben . msdn.microsoft.com/en-us/library/… Hat jedoch Collection<T>eine.
Tim Goodman
@ TimGoodman - Das ist die nicht generische Schnittstelle. Siehe msdn.microsoft.com/en-us/library/92t2ye13.aspx
TrueWill
"Das Ändern vorhandener Sammlungen fällt wirklich nicht in den Bereich des beabsichtigten Zwecks von LINQ." @ Levi Dann warum überhaupt Add(T item)? Scheint ein halbherziger Ansatz zu sein, der die Möglichkeit bietet, ein einzelnes Element hinzuzufügen und dann zu erwarten, dass alle Anrufer iterieren, um mehr als ein Element gleichzeitig hinzuzufügen. Ihre Aussage ist sicherlich richtig, IEnumerable<T>aber ich war ICollectionsmehr als einmal frustriert . Ich bin nicht anderer Meinung als Sie, nur entlüften.
Akousmata

Antworten:

62

Nein, das scheint völlig vernünftig. Es gibt eine List<T>.AddRange () -Methode, die im Grunde genau dies tut, aber erfordert, dass Ihre Sammlung konkret ist List<T>.

Reed Copsey
quelle
1
Vielen Dank; Sehr wahr, aber die meisten öffentlichen Immobilien folgen den MS-Richtlinien und sind keine Listen.
TrueWill
7
Ja - ich habe es mehr als Begründung dafür gegeben, warum ich nicht glaube, dass es ein Problem damit gibt. Stellen Sie nur fest, dass es weniger effizient ist als die List <T> -Version (da die List <T> vorab zugewiesen werden kann)
Reed Copsey
Achten Sie nur darauf, dass die AddRange-Methode in .NET Core 2.2 bei falscher Verwendung ein seltsames Verhalten zeigt, wie in dieser Ausgabe gezeigt: github.com/dotnet/core/issues/2667
Bruno
36

Versuchen Sie, in der Erweiterungsmethode in List umzuwandeln, bevor Sie die Schleife ausführen. Auf diese Weise können Sie die Leistung von List.AddRange nutzen.

public static void AddRange<T>(this ICollection<T> destination,
                               IEnumerable<T> source)
{
    List<T> list = destination as List<T>;

    if (list != null)
    {
        list.AddRange(source);
    }
    else
    {
        foreach (T item in source)
        {
            destination.Add(item);
        }
    }
}
Rymdsmurf
quelle
2
Der asBediener wird niemals werfen. Wenn destinationnicht umgewandelt werden kann, listist null und der elseBlock wird ausgeführt.
Rymdsmurf
4
arrgggh! Tauschen Sie die Zustandszweige gegen die Liebe zu allem, was heilig ist!
nicodemus13
13
Eigentlich meine ich es ernst. Der Hauptgrund ist, dass es eine zusätzliche kognitive Belastung ist, die oft sehr schwierig ist. Sie versuchen ständig, negative Bedingungen zu bewerten, was normalerweise relativ schwierig ist. Sie haben sowieso beide Zweige. Es ist (IMO) einfacher zu sagen, wenn null dies tut, tun Sie dies anders, als das Gegenteil. Es geht auch um Standardeinstellungen. Sie sollten so oft wie möglich das positive Konzept sein. habe das, also ist der andere Zweig, wenn er deaktiviert ist). Schwer zu analysieren.
nicodemus13
13
Die Interpretation von "etwas! = Null" ist nicht schwieriger als die Interpretation von "etwas == null". Der Negationsoperator ist jedoch eine völlig andere Sache, und in Ihrem letzten Beispiel würde das Umschreiben der if-else-Anweisung diesen Operator eliminieren . Das ist objektiv eine Verbesserung, die aber nicht mit der ursprünglichen Frage zusammenhängt. In diesem speziellen Fall sind die beiden Formen eine Frage persönlicher Vorlieben, und ich würde den Operator "! =" Vorziehen, wenn man die obigen Überlegungen berücksichtigt.
Rymdsmurf
14
if (destination is List<T> list)
Mustervergleich
28

Da , .NET4.5wenn Sie Einzeiler möchten Sie verwenden können System.Collections.GenericFürJeden.

source.ForEach(o => destination.Add(o));

oder noch kürzer als

source.ForEach(destination.Add);

In Bezug auf die Leistung ist es das gleiche wie für jede Schleife (syntaktischer Zucker).

Versuchen Sie auch nicht , es wie zuzuweisen

var x = source.ForEach(destination.Add) 

Ursache ForEachist nichtig.

Bearbeiten: Aus Kommentaren kopiert , Liperts Meinung zu ForEach

Matas Vaitkevicius
quelle
9
Persönlich bin ich mit Lippert in diesem Fall
TrueWill
1
Sollte es source.ForEach (destination.Add) sein?
Frank
4
ForEachscheint nur definiert zu sein List<T>, nicht Collection?
Beschützer ein
Lippert kann jetzt unter web.archive.org/web/20190316010649/https://…
user7610
Aktualisierter Link zu Eric Lipperts Blog-Beitrag: Fabulous Adventures in Coding | "Foreach" gegen "ForEach"
Alexander
19

Denken Sie daran, dass jeder Adddie Kapazität der Sammlung überprüft und bei Bedarf die Größe ändert (langsamer). Mit AddRangewird die Sammlung die Kapazität eingestellt und dann die Elemente hinzugefügt (schneller). Diese Erweiterungsmethode ist extrem langsam, funktioniert aber.

jvitor83
quelle
3
Hinzu kommt, dass für jede Hinzufügung eine Benachrichtigung über Sammlungsänderungen angezeigt wird, im Gegensatz zu einer Massenbenachrichtigung mit AddRange.
Nick Udell
3

Hier ist eine etwas fortgeschrittenere / produktionsbereite Version:

    public static class CollectionExtensions
    {
        public static TCol AddRange<TCol, TItem>(this TCol destination, IEnumerable<TItem> source)
            where TCol : ICollection<TItem>
        {
            if(destination == null) throw new ArgumentNullException(nameof(destination));
            if(source == null) throw new ArgumentNullException(nameof(source));

            // don't cast to IList to prevent recursion
            if (destination is List<TItem> list)
            {
                list.AddRange(source);
                return destination;
            }

            foreach (var item in source)
            {
                destination.Add(item);
            }

            return destination;
        }
    }
MovGP0
quelle
1

Die C5 generische Sammlungen Bibliothek Klassen alle unterstützen AddRangeMethode. C5 verfügt über eine viel robustere Schnittstelle, die tatsächlich alle Funktionen der zugrunde liegenden Implementierungen verfügbar macht und mit den Schnittstellen System.Collections.Generic ICollectionund kompatibel ist. Dies IListbedeutet, dass C5die Sammlungen leicht als zugrunde liegende Implementierung ersetzt werden können.

Marcus Griep
quelle
0

Sie können Ihren IEnumerable-Bereich zu einer Liste hinzufügen und dann die ICollection = auf die Liste setzen.

        IEnumerable<T> source;

        List<item> list = new List<item>();
        list.AddRange(source);

        ICollection<item> destination = list;
Jonathan Jansen
quelle
3
Während dies funktional funktioniert, verstößt es gegen die Microsoft-Richtlinien, um Sammlungseigenschaften schreibgeschützt zu machen ( msdn.microsoft.com/en-us/library/ms182327.aspx )
Nick Udell
0

Oder Sie können einfach eine ICollection-Erweiterung wie folgt erstellen:

 public static ICollection<T> AddRange<T>(this ICollection<T> @this, IEnumerable<T> items)
    {
        foreach(var item in items)
        {
            @this.Add(item);
        }

        return @this;
    }

Die Verwendung wäre wie die Verwendung in einer Liste:

collectionA.AddRange(IEnumerable<object> items);
Katarina Kelam
quelle