jak dodać zakres elementów do zmiennej IList

81

nie ma na to AddRange()metody IList<T>.

Jak mogę dodać listę elementów do a IList<T>bez iteracji po elementach i używania Add()metody?

mohsen dorparasti
źródło

Odpowiedzi:

66

AddRangejest zdefiniowany w List<T>interfejsie, a nie w interfejsie.

Możesz zadeklarować zmienną jako List<T>zamiast IList<T>lub rzutować ją na List<T>, aby uzyskać dostęp do AddRange.

((List<myType>)myIList).AddRange(anotherList);

To nie jest dobra praktyka (patrz komentarze poniżej), ponieważ IList<T>może nie być a List<T>, ale jakiś inny typ, który zaimplementował interfejs i może bardzo nie mieć AddRangemetody - w takim przypadku dowiesz się tylko, gdy twój kod wyrzuci wyjątek w czasie wykonywania.

Tak więc, chyba że wiesz na pewno, że typ jest rzeczywiście typem, List<T>którego nie powinieneś próbować używać AddRange.

Jednym ze sposobów jest przetestowanie typu za pomocą operatorów is lub as (od C # 7).

if(myIList is List<T>)
{
   // can cast and AddRange
}
else
{
   // iterate with Add
}
Oded
źródło
2
@ mohsen.d - Jeśli typ jest generowany, nie chcesz zmieniać wygenerowanego kodu (ponieważ może on zostać nadpisany). ConcatPrzesyłaj lub używaj LINQ , jak odpowiedział @Self_Taught_Programmer .
Oded
2
@ mohsen.d - Jeśli to twój kod, równie dobrze możesz zadeklarować typ jako List<T>(lub, jeśli nie jest to dobry wybór, wykonaj rzutowanie tam, gdzie chcesz, AddRangeaby był zlokalizowany - jest to operacja bardzo tania ).
Oded
53
Nie nie nie. Powodem, dla którego jest to IList <T> na początek, jest to, że może to być coś innego niż implementacja List <T>. Napisz metodę rozszerzenia, jak pokazano w BlackjacketMack, jeśli naprawdę potrzebujesz metody AddRange.
Derek Greer
6
Nie mam pojęcia, dlaczego otrzymało tak wiele głosów za, ponieważ wyraźnie wyrzuci coś podobnego, InvalidCastExceptionjeśli zostanie użyte na czymkolwiek innym niż List<T>(na przykład tablica).
bashis
3
Ale casting nie jest dobrym pomysłem. Może to prowadzić do zwiększenia wydajności.
Gul Ershad
79

Jeśli spojrzysz na kod źródłowy C # dla List , myślę, że List.AddRange () ma optymalizacje, których nie rozwiązuje prosta pętla. Tak więc metoda rozszerzająca powinna po prostu sprawdzić, czy IList jest Listą, a jeśli tak, użyj jej natywnej AddRange ().

Przeglądając kod źródłowy, widzisz, że ludzie .NET robią podobne rzeczy we własnych rozszerzeniach Linq dla rzeczy takich jak .ToList () (jeśli jest to lista, rzuć ją ... w przeciwnym razie utwórz ją).

public static class IListExtension
{
    public static void AddRange<T>(this IList<T> list, IEnumerable<T> items)
    {
        if (list == null) throw new ArgumentNullException(nameof(list));
        if (items == null) throw new ArgumentNullException(nameof(items));

        if (list is List<T> asList)
        {
            asList.AddRange(items);
        }
        else
        {
            foreach (var item in items)
            {
                list.Add(item);
            }
        }
    }
}
BlackjacketMack
źródło
6
Z punktu widzenia optymalizacji, jesteś rzeczywiście odlewania listdo List<T>dwóch razy tutaj. Jeden z nich można zoptymalizować za pomocą assłowa kluczowego.
bashis
Dobre połączenie @bashis. Zawsze rozważam cenę podwójnego rzutu w porównaniu z czyszczeniem GC naszego nowego var. Ale rzeczywiście moglibyśmy zrobić var ​​listCasted = list jako List <T>; if (listCasted! = null) ... Może wyrażenia deklaracji C # 6 zmienią ten wzorzec: if (myVar.As (out myVarCasted)) myVarCasted ...)
BlackjacketMack
Czy możesz zaktualizować kod za pomocą wspomnianej optymalizacji? Nie jestem jeszcze tak biegły w najnowszych funkcjach. @BlackjacketMack
2
@zuckerthoben - właśnie przeprowadziłem test obejmujący milion iteracji przy użyciu obu podejść i nie było różnicy w wydajności. Więc nie nazwałbym tego optymalizacją ... dodatkowo dodaje linię kodu (ale zmniejsza rzutowanie parens). W każdym razie prawdopodobnie użyłbym obecnie „as”: var listCasted = list as List <T>; if (listCasted! = null) {listCasted.AddRange (items);}. Nie warto aktualizować odpowiedzi IMHO, ale dobrze jest wprowadzić jako syntaktyczną alternatywę.
BlackjacketMack
2
Od teraz możesz to zrobićif (list is List<T> castedList) { castedList.AddRange(items); }
André Mantas
23

Możesz zrobić coś takiego:

IList<string> oIList1 = new List<string>{"1","2","3"};
IList<string> oIList2 = new List<string>{"4","5","6"};
IList<string> oIList3 = oIList1.Concat(oIList2).ToList();

Zasadniczo używałbyś Concat()rozszerzenia i uzyskałby ToList()podobną funkcjonalność jak AddRange().

Źródło

Rayshawn
źródło
1
Problem z twoim podejściem polega na tym, że Enumerable.Concatjest on implementowany przez System.Linq.Enumerablei wartość zwracana przez tę metodę jest IEnumerable<TSource>taka, więc uważam, że nie powinno się jej odwracać IList<TSource>- może zwrócić coś innego z powodu szczegółów implementacji, których nie znamy bez sprawdzenia kodu źródłowego - mimo to nie ma gwarancji, że to się nie zmieni - dlatego należy zwrócić szczególną uwagę na obsługę wielu wersji .NET.
jweyrich
8

Możesz również napisać taką metodę rozszerzającą:

internal static class EnumerableHelpers
{
    public static void AddRange<T>(this IList<T> collection, IEnumerable<T> items)
    {
        foreach (var item in items)
        {
            collection.Add(item);
        }
    }
}

Stosowanie:

        IList<int> collection = new int[10]; //Or any other IList
        var items = new[] {1, 4, 5, 6, 7};
        collection.AddRange(items);

Co nadal jest iteracją po elementach, ale nie musisz pisać iteracji ani rzutować za każdym razem, gdy ją wywołasz.

bashis
źródło
2

Inna odpowiedź za pomocą LINQ, pod warunkiem, że dodawana rzecz to a List<T>lub możesz do niej zadzwonić ToList():

IEnumerable<string> toAdd = new string[] {"a", "b", "c"};
IList<string> target = new List<string>();

toAdd.ToList().ForEach(target.Add);
Czerwony deszcz
źródło