Un collègue m'a demandé aujourd'hui comment ajouter une plage à une collection. Il a une classe qui hérite de Collection<T>
. Il existe une propriété get-only de ce type qui contient déjà certains éléments. Il souhaite ajouter les éléments d'une autre collection à la collection de propriétés. Comment peut-il le faire d'une manière conviviale en C # 3? (Notez la contrainte relative à la propriété get-only, qui empêche des solutions telles que l'Union et la réaffectation.)
Bien sûr, une foreach avec la propriété. Ajouter fonctionnera. Mais un List<T>
- style AddRange serait beaucoup plus élégant.
C'est assez facile d'écrire une méthode d'extension:
public static class CollectionHelpers
{
public static void AddRange<T>(this ICollection<T> destination,
IEnumerable<T> source)
{
foreach (T item in source)
{
destination.Add(item);
}
}
}
Mais j'ai le sentiment de réinventer la roue. Je n'ai rien trouvé de semblable dans System.Linq
ou morelinq .
Mauvais design? Il suffit d'appeler Ajouter? Manquer l'évidence?
Non, cela semble parfaitement raisonnable. Il existe une méthode List<T>.
AddRange () qui fait essentiellement cela, mais nécessite que votre collection soit un List<T>
concret.
Essayez de transtyper List dans la méthode d'extension avant d'exécuter la boucle. De cette façon, vous pouvez tirer parti des performances de List.AddRange.
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);
}
}
}
Depuis .NET4.5
si vous voulez une ligne, vous pouvez utiliser System.Collections.Generic
ForEach.
source.ForEach(o => destination.Add(o));
ou même plus court que
source.ForEach(destination.Add);
En termes de performances, c'est la même chose que pour chaque boucle (sucre syntaxique).
Aussi ne pas essayer de l'assigner comme
var x = source.ForEach(destination.Add)
cause ForEach
est nul.
N'oubliez pas que chaque Add
vérifiera la capacité de la collection et la redimensionnera si nécessaire (plus lentement). Avec AddRange
, la collection sera définie la capacité, puis ajoutée aux éléments (plus rapidement). Cette méthode d'extension sera extrêmement lente, mais fonctionnera.
Voici une version un peu plus avancée/prête pour la production:
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;
}
}
Les classes C5 Generic Collections Library supportent toutes la méthode AddRange
. C5 a une interface beaucoup plus robuste qui expose en réalité toutes les fonctionnalités de ses implémentations sous-jacentes et est compatible avec les interfaces System.Collections.Generic
ICollection
et IList
, ce qui signifie que les collections de C5
peuvent facilement être substituées à l'implémentation sous-jacente.
Vous pouvez ajouter votre plage IEnumerable à une liste, puis définir ICollection = sur la liste.
IEnumerable<T> source;
List<item> list = new List<item>();
list.AddRange(source);
ICollection<item> destination = list;
Ou vous pouvez simplement créer une extension ICollection comme ceci:
public static ICollection<T> AddRange<T>(this ICollection<T> @this, IEnumerable<T> items)
{
foreach(var item in items)
{
@this.Add(item);
}
return @this;
}
L’utiliser serait comme l’utiliser sur une liste:
collectionA.AddRange(IEnumerable<object> items);