Wie entferne ich Elemente aus einer generischen Liste, während ich darüber iteriere?


451

Ich suche nach einem besseren Muster für die Arbeit mit einer Liste von Elementen, die jeweils verarbeitet werden müssen und dann je nach Ergebnis aus der Liste entfernt werden.

Sie können nicht .Remove(element)innerhalb von verwenden foreach (var element in X)(weil dies zu einer Collection was modified; enumeration operation may not execute.Ausnahme führt) ... Sie können auch nicht verwendenfor (int i = 0; i < elements.Count(); i++) und .RemoveAt(i)weil es Ihre aktuelle Position in der Sammlung relativ zu stört i.

Gibt es eine elegante Möglichkeit, dies zu tun?

Antworten:


734

Iterieren Sie Ihre Liste in umgekehrter Reihenfolge mit einer for-Schleife:

for (int i = safePendingList.Count - 1; i >= 0; i--)
{
    // some code
    // safePendingList.RemoveAt(i);
}

Beispiel:

var list = new List<int>(Enumerable.Range(1, 10));
for (int i = list.Count - 1; i >= 0; i--)
{
    if (list[i] > 5)
        list.RemoveAt(i);
}
list.ForEach(i => Console.WriteLine(i));

Alternativ können Sie die RemoveAll-Methode mit einem Prädikat verwenden, um Folgendes zu testen:

safePendingList.RemoveAll(item => item.Value == someValue);

Hier ist ein vereinfachtes Beispiel, um zu demonstrieren:

var list = new List<int>(Enumerable.Range(1, 10));
Console.WriteLine("Before:");
list.ForEach(i => Console.WriteLine(i));
list.RemoveAll(i => i > 5);
Console.WriteLine("After:");
list.ForEach(i => Console.WriteLine(i));

19
Für diejenigen, die aus Java kommen, ist die Liste von C # wie ArrayList, da das Einfügen / Entfernen O (n) und das Abrufen über einen Index O (1) ist. Dies ist keine traditionelle verknüpfte Liste. Es scheint ein bisschen unglücklich, dass C # das Wort "Liste" verwendet, um diese Datenstruktur zu beschreiben, da es an die klassische verknüpfte Liste erinnert.
Jarrod Smith

79
Nichts im Namen 'List' sagt 'LinkedList'. Personen, die aus anderen Sprachen als Java stammen, sind möglicherweise verwirrt, wenn es sich um eine verknüpfte Liste handelt.
GolezTrol

5
Ich bin hier durch eine vb.net-Suche gelandet, falls jemand die vb.net-äquivalente Syntax für RemoveAll haben möchte:list.RemoveAll(Function(item) item.Value = somevalue)
Pseudocoder

1
Dies funktioniert auch in Java mit minimalen Änderungen: .size()anstelle von .Countund .remove(i)anstelle von .removeAt(i). Clever - danke!
Herbst Leonard

2
Ich habe einen kleinen Leistungstest durchgeführt und es stellte sich heraus, dass dies RemoveAll()dreimal so lange dauert wie die Rückwärtsschleife for. Ich bleibe also definitiv bei der Schleife, zumindest in Abschnitten, in denen es darauf ankommt.
Crusha K. Rool

84

Eine einfache und unkomplizierte Lösung:

Verwenden Sie eine Standard-for-Schleife, die in Ihrer Sammlung rückwärts ausgeführt wird, und RemoveAt(i)entfernen Sie Elemente.


2
Beachten Sie, dass das Entfernen von Elementen nacheinander nicht effizient ist, wenn Ihre Liste viele Elemente enthält. Es kann O (n ^ 2) sein. Stellen Sie sich eine Liste mit zwei Milliarden Elementen vor, in der die ersten Milliarden Elemente gelöscht werden. Bei jeder Entfernung werden alle späteren Elemente kopiert, sodass Sie am Ende jeweils eine Milliarde Elemente milliardenfach kopieren. Dies liegt nicht an der umgekehrten Iteration, sondern an der einzelnen Entfernung. RemoveAll stellt sicher, dass jedes Element höchstens einmal kopiert wird, sodass es linear ist. Die einmalige Entfernung kann milliardenfach langsamer sein. O (n) gegen O (n ^ 2).
Bruce Dawson

71

Die umgekehrte Iteration sollte als Erstes in den Sinn kommen, wenn Sie Elemente aus einer Sammlung entfernen möchten, während Sie darüber iterieren.

Glücklicherweise gibt es eine elegantere Lösung als das Schreiben einer for-Schleife, die unnötiges Tippen erfordert und fehleranfällig sein kann.

ICollection<int> test = new List<int>(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});

foreach (int myInt in test.Reverse<int>())
{
    if (myInt % 2 == 0)
    {
        test.Remove(myInt);
    }
}

7
Das hat bei mir perfekt funktioniert. Einfach und elegant und erfordert nur minimale Änderungen an meinem Code.
Stephen MacDougall

1
Ist das nur ein flippiges Genie? Und ich stimme @StephenMacDougall zu, ich muss diese C ++ 'y nicht für Schleifen verwenden und einfach wie beabsichtigt mit LINQ weitermachen.
Piotr Kula

5
Ich sehe keinen Vorteil gegenüber einfachem foreach (int myInt in test.ToList ()) {if (myInt% 2 == 0) {test.Remove (myInt); }} Sie müssen noch eine Kopie für Reverse zuweisen und es wird Huh? Moment - warum gibt es Reverse.
Jahav

11
@jedesah Ja, Reverse<T>()erstellt einen Iterator, der die Liste rückwärts durchläuft, ihm jedoch zusätzlichen Puffer mit derselben Größe wie die Liste selbst zuweist ( referencesource.microsoft.com/#System.Core/System/Linq/… ). Reverse<T>geht die ursprüngliche Liste nicht nur in umgekehrter Reihenfolge durch (ohne zusätzlichen Speicher zuzuweisen). Daher haben beide ToList()und Reverse()den gleichen Speicherverbrauch (beide erstellen eine Kopie), ToList()tun aber nichts mit den Daten. Mit Reverse<int>()würde ich mich fragen, warum die Liste aus welchem ​​Grund umgekehrt ist.
Jahav

1
@ Jahav Ich verstehe deinen Standpunkt. Das ist ziemlich enttäuschend, dass die Implementierung von Reverse<T>()einen neuen Puffer schafft. Ich bin mir nicht ganz sicher, ob ich verstehe, warum das notwendig ist. Es scheint mir, dass es abhängig von der zugrunde liegenden Struktur der Enumerablezumindest in einigen Fällen möglich sein sollte, eine umgekehrte Iteration zu erreichen, ohne linearen Speicher zuzuweisen.
jedesah

68
 foreach (var item in list.ToList()) {
     list.Remove(item);
 }

Wenn Sie ".ToList ()" zu Ihrer Liste hinzufügen (oder die Ergebnisse einer LINQ-Abfrage), können Sie "item" direkt aus "list" entfernen, ohne dass die gefürchtete " Collection wurde geändert; Aufzählungsoperation wird möglicherweise nicht ausgeführt ." Error. Der Compiler erstellt eine Kopie von "list", damit Sie das Array sicher entfernen können.

Obwohl dieses Muster nicht sehr effizient ist, fühlt es sich natürlich an und ist für fast jede Situation flexibel genug . Zum Beispiel, wenn Sie jedes "Element" in einer Datenbank speichern und erst dann aus der Liste entfernen möchten, wenn die DB-Speicherung erfolgreich ist.


6
Dies ist die beste Lösung, wenn Effizienz nicht entscheidend ist.
IvanP

2
Dies ist auch schneller und besser lesbar: list.RemoveAll (i => true);
Santiago Aristi

1
@ Greg Little, habe ich Sie richtig verstanden - wenn Sie ToList () hinzufügen, durchläuft der Compiler die kopierte Sammlung, entfernt sie jedoch aus dem Original?
Pyrejkee

Wenn Ihre Liste doppelte Elemente enthält und Sie nur das später in der Liste vorkommende Element entfernen möchten, wird dann nicht das falsche Element entfernt?
Tim MB

Funktioniert dies auch für eine Liste von Objekten?
Tom el Safadi

23

Wählen Sie die gewünschten Elemente aus , anstatt zu versuchen, die nicht gewünschten Elemente zu entfernen . Dies ist so viel einfacher (und im Allgemeinen auch effizienter) als das Entfernen von Elementen.

var newSequence = (from el in list
                   where el.Something || el.AnotherThing < 0
                   select el);

Ich wollte dies als Kommentar als Antwort auf den Kommentar von Michael Dillon unten posten, aber es ist zu lang und wahrscheinlich nützlich, um es trotzdem in meiner Antwort zu haben:

Persönlich würde ich niemals Elemente einzeln entfernen, wenn Sie sie entfernen müssen, und dann einen Aufruf aufrufen, RemoveAllder ein Prädikat verwendet und das interne Array nur einmal neu anordnet, während für jedes Element, das Sie entfernen, Removeeine Array.CopyOperation ausgeführt wird. RemoveAllist weitaus effizienter.

Und wenn Sie eine Liste rückwärts durchlaufen, haben Sie bereits den Index des Elements, das Sie entfernen möchten, sodass das Aufrufen weitaus effizienter ist RemoveAt, da Removezuerst die Liste durchlaufen wird, um den Index des Elements zu finden, das Sie verwenden Ich versuche zu entfernen, aber Sie kennen diesen Index bereits.

Alles in allem sehe ich keinen Grund, jemals Removeeine for-Schleife aufzurufen . Und wenn es überhaupt möglich ist, verwenden Sie im Idealfall den obigen Code, um Elemente aus der Liste nach Bedarf zu streamen, sodass überhaupt keine zweite Datenstruktur erstellt werden muss.


1
Entweder dies, oder Sie fügen einen Zeiger auf die unerwünschten Elemente in eine zweite Liste ein. Wiederholen Sie nach dem Ende Ihrer Schleife die Entfernungsliste und entfernen Sie damit die Elemente.
Michael Dillon

22

Wenn Sie ToArray () in einer generischen Liste verwenden, können Sie ein Entfernen (Element) in Ihrer generischen Liste ausführen:

        List<String> strings = new List<string>() { "a", "b", "c", "d" };
        foreach (string s in strings.ToArray())
        {
            if (s == "b")
                strings.Remove(s);
        }

2
Dies ist nicht falsch, aber ich muss darauf hinweisen, dass dadurch die Notwendigkeit umgangen wird, eine zweite "Speicher" -Liste der Elemente zu erstellen, die auf Kosten des Kopierens der gesamten Liste in ein Array entfernt werden sollen. Eine zweite Liste handverlesener Elemente enthält wahrscheinlich weniger Elemente.
Ahmad Mageed

20

Mit .ToList () wird eine Kopie Ihrer Liste erstellt, wie in dieser Frage erläutert: ToList () - Erstellt es eine neue Liste?

Mit ToList () können Sie aus Ihrer ursprünglichen Liste entfernen, da Sie tatsächlich über eine Kopie iterieren.

foreach (var item in listTracked.ToList()) {    

        if (DetermineIfRequiresRemoval(item)) {
            listTracked.Remove(item)
        }

     }

1
Unter Performance-Gesichtspunkten kopieren Sie jedoch Ihre Liste, was einige Zeit dauern kann. Gute und einfache Möglichkeit, aber mit einer nicht so guten Leistung
Florian K

12

Wenn die Funktion, die bestimmt, welche Elemente gelöscht werden sollen, keine Nebenwirkungen hat und das Element nicht mutiert (es ist eine reine Funktion), lautet eine einfache und effiziente Lösung (lineare Zeit):

list.RemoveAll(condition);

Wenn es Nebenwirkungen gibt, würde ich etwas verwenden wie:

var toRemove = new HashSet<T>();
foreach(var item in items)
{
     ...
     if(condition)
          toRemove.Add(item);
}
items.RemoveAll(toRemove.Contains);

Dies ist immer noch eine lineare Zeit, vorausgesetzt, der Hash ist gut. Aufgrund des Hash-Sets ist die Speichernutzung jedoch erhöht.

Wenn Ihre Liste nur eine IList<T>anstelle einer Liste ist, List<T>schlage ich meine Antwort auf Wie kann ich diesen speziellen foreach-Iterator ausführen? . Dies hat eine lineare Laufzeit bei typischen Implementierungen von IList<T>im Vergleich zur quadratischen Laufzeit vieler anderer Antworten.


11

Da jede Entfernung unter einer Bedingung vorgenommen wird, die Sie verwenden können

list.RemoveAll(item => item.Value == someValue);

Dies ist die beste Lösung, wenn die Verarbeitung den Artikel nicht mutiert und keine Nebenwirkungen hat.
CodesInChaos

9
List<T> TheList = new List<T>();

TheList.FindAll(element => element.Satisfies(Condition)).ForEach(element => TheList.Remove(element));

8

Sie können foreach nicht verwenden, aber Sie können vorwärts iterieren und Ihre Schleifenindexvariable verwalten, wenn Sie ein Element entfernen, wie folgt:

for (int i = 0; i < elements.Count; i++)
{
    if (<condition>)
    {
        // Decrement the loop counter to iterate this index again, since later elements will get moved down during the remove operation.
        elements.RemoveAt(i--);
    }
}

Beachten Sie, dass im Allgemeinen alle diese Techniken vom Verhalten der Sammlung abhängen, die iteriert wird. Die hier gezeigte Technik funktioniert mit der Standardliste (T). (Es ist durchaus möglich , Ihre eigene Sammlung Klasse zu schreiben und Iterator , dass tut erlaubt Artikel Entfernen während einer foreach - Schleife.)


6

Die Verwendung Removeoder RemoveAtBearbeitung einer Liste beim Durchlaufen dieser Liste wurde absichtlich erschwert, da dies fast immer falsch ist . Sie könnten es vielleicht mit einem cleveren Trick zum Laufen bringen, aber es wäre extrem langsam. Jedes Mal, wenn Sie anrufen Remove, muss die gesamte Liste durchsucht werden, um das Element zu finden, das Sie entfernen möchten. Jedes Mal, wenn Sie aufrufen RemoveAt, müssen die nachfolgenden Elemente um 1 Position nach links verschoben werden. Als solche würde jede Lösung, die Removeoder verwendet RemoveAt, eine quadratische Zeit erfordern, O (n²) .

Verwenden RemoveAllSie, wenn Sie können. Andernfalls filtert das folgende Muster die Liste an Ort und Stelle in der linearen Zeit O (n) .

// Create a list to be filtered
IList<int> elements = new List<int>(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
// Filter the list
int kept = 0;
for (int i = 0; i < elements.Count; i++) {
    // Test whether this is an element that we want to keep.
    if (elements[i] % 3 > 0) {
        // Add it to the list of kept elements.
        elements[kept] = elements[i];
        kept++;
    }
}
// Unfortunately IList has no Resize method. So instead we
// remove the last element of the list until: elements.Count == kept.
while (kept < elements.Count) elements.RemoveAt(elements.Count-1);

3

Ich wünschte, das "Muster" wäre ungefähr so:

foreach( thing in thingpile )
{
    if( /* condition#1 */ )
    {
        foreach.markfordeleting( thing );
    }
    elseif( /* condition#2 */ )
    {
        foreach.markforkeeping( thing );
    }
} 
foreachcompleted
{
    // then the programmer's choices would be:

    // delete everything that was marked for deleting
    foreach.deletenow(thingpile); 

    // ...or... keep only things that were marked for keeping
    foreach.keepnow(thingpile);

    // ...or even... make a new list of the unmarked items
    others = foreach.unmarked(thingpile);   
}

Dies würde den Code an dem Prozess ausrichten, der im Gehirn des Programmierers abläuft.


1
Leicht genug. Erstellen Sie einfach ein boolesches Flag-Array (verwenden Sie einen 3-Status-Typ, z. B. Nullable<bool>wenn Sie nicht markierte zulassen möchten) und verwenden Sie es nach dem foreach, um Elemente zu entfernen / zu behalten.
Dan Bechard

3

Unter der Annahme, dass das Prädikat eine boolesche Eigenschaft eines Elements ist, sollte das Element entfernt werden, wenn es wahr ist:

        int i = 0;
        while (i < list.Count())
        {
            if (list[i].predicate == true)
            {
                list.RemoveAt(i);
                continue;
            }
            i++;
        }

Ich habe dies positiv bewertet, da es manchmal effizienter sein kann, die Liste in der richtigen Reihenfolge zu durchlaufen (nicht in umgekehrter Reihenfolge). Vielleicht könnten Sie aufhören, wenn Sie das erste Element finden, das nicht entfernt werden soll, weil die Liste bestellt ist. (Stellen Sie sich eine 'Pause' vor, in der sich das i ++ in diesem Beispiel befindet.
FrankKrumnow

3

Ich würde die Liste aus einer LINQ-Abfrage neu zuweisen, die die Elemente herausfiltert, die Sie nicht behalten möchten.

list = list.Where(item => ...).ToList();

Sofern die Liste nicht sehr umfangreich ist, sollten dabei keine wesentlichen Leistungsprobleme auftreten.


3

Der beste Weg, um Elemente aus einer Liste zu entfernen, während Sie darüber iterieren, ist die Verwendung RemoveAll() . Das Hauptanliegen der Menschen ist jedoch, dass sie einige komplexe Dinge innerhalb der Schleife tun und / oder komplexe Vergleichsfälle haben müssen.

Die Lösung besteht darin RemoveAll(), diese Notation weiterhin zu verwenden, aber zu verwenden:

var list = new List<int>(Enumerable.Range(1, 10));
list.RemoveAll(item => 
{
    // Do some complex operations here
    // Or even some operations on the items
    SomeFunction(item);
    // In the end return true if the item is to be removed. False otherwise
    return item > 5;
});

2
foreach(var item in list.ToList())

{

if(item.Delete) list.Remove(item);

}

Erstellen Sie einfach eine völlig neue Liste aus der ersten. Ich sage "Einfach" statt "Richtig", da das Erstellen einer völlig neuen Liste wahrscheinlich einen Leistungsaufschlag gegenüber der vorherigen Methode hat (ich habe mich nicht um Benchmarking gekümmert). Ich bevorzuge im Allgemeinen dieses Muster, es kann auch nützlich sein, um es zu überwinden Einschränkungen von Linq-To-Entities.

for(i = list.Count()-1;i>=0;i--)

{

item=list[i];

if (item.Delete) list.Remove(item);

}

Auf diese Weise wird die Liste mit einer einfachen alten For-Schleife rückwärts durchlaufen. Dies vorwärts zu tun könnte problematisch sein, wenn sich die Größe der Sammlung ändert, aber rückwärts sollte immer sicher sein.


1

Kopieren Sie die Liste, die Sie iterieren. Dann aus der Kopie entfernen und das Original interatieren. Rückwärtsgehen ist verwirrend und funktioniert nicht gut, wenn Sie parallel schleifen.

var ids = new List<int> { 1, 2, 3, 4 };
var iterableIds = ids.ToList();

Parallel.ForEach(iterableIds, id =>
{
    ids.Remove(id);
});

1

Das würde mir gefallen

using System.IO;
using System;
using System.Collections.Generic;

class Author
    {
        public string Firstname;
        public string Lastname;
        public int no;
    }

class Program
{
    private static bool isEven(int i) 
    { 
        return ((i % 2) == 0); 
    } 

    static void Main()
    {    
        var authorsList = new List<Author>()
        {
            new Author{ Firstname = "Bob", Lastname = "Smith", no = 2 },
            new Author{ Firstname = "Fred", Lastname = "Jones", no = 3 },
            new Author{ Firstname = "Brian", Lastname = "Brains", no = 4 },
            new Author{ Firstname = "Billy", Lastname = "TheKid", no = 1 }
        };

        authorsList.RemoveAll(item => isEven(item.no));

        foreach(var auth in authorsList)
        {
            Console.WriteLine(auth.Firstname + " " + auth.Lastname);
        }
    }
}

AUSGABE

Fred Jones
Billy TheKid

0

Ich befand mich in einer ähnlichen Situation, in der ich jedes n- te Element in einer bestimmten entfernen musste List<T>.

for (int i = 0, j = 0, n = 3; i < list.Count; i++)
{
    if ((j + 1) % n == 0) //Check current iteration is at the nth interval
    {
        list.RemoveAt(i);
        j++; //This extra addition is necessary. Without it j will wrap
             //down to zero, which will throw off our index.
    }
    j++; //This will always advance the j counter
}

0

Die Kosten für das Entfernen eines Elements aus der Liste sind proportional zur Anzahl der Elemente, die auf das zu entfernende Element folgen. In dem Fall, in dem die erste Hälfte der Elemente zum Entfernen qualifiziert ist, muss jeder Ansatz, der auf dem individuellen Entfernen von Elementen basiert, etwa N * N / 4 Elementkopiervorgänge ausführen, was bei einer großen Liste sehr teuer werden kann .

Ein schnellerer Ansatz besteht darin, die Liste zu durchsuchen, um das erste zu entfernende Element zu finden (falls vorhanden), und dann von diesem Punkt an jedes Element, das aufbewahrt werden soll, an die Stelle zu kopieren, zu der es gehört. Wenn dies erledigt ist und R-Elemente beibehalten werden sollen, sind die ersten R-Elemente in der Liste diese R-Elemente, und alle Elemente, die gelöscht werden müssen, befinden sich am Ende. Wenn diese Elemente in umgekehrter Reihenfolge gelöscht werden, muss das System keines von ihnen kopieren. Wenn die Liste also N Elemente enthält, von denen R Elemente, einschließlich aller ersten F, beibehalten wurden, ist dies erforderlich Kopieren Sie RF-Elemente und verkleinern Sie die Liste um ein Element NR-mal. Alle lineare Zeit.


0

Mein Ansatz ist, dass ich zuerst eine Liste von Indizes erstelle, die gelöscht werden sollen. Danach durchlaufe ich die Indizes und entferne die Elemente aus der Anfangsliste. Das sieht so aus:

var messageList = ...;
// Restrict your list to certain criteria
var customMessageList = messageList.FindAll(m => m.UserId == someId);

if (customMessageList != null && customMessageList.Count > 0)
{
    // Create list with positions in origin list
    List<int> positionList = new List<int>();
    foreach (var message in customMessageList)
    {
        var position = messageList.FindIndex(m => m.MessageId == message.MessageId);
        if (position != -1)
            positionList.Add(position);
    }
    // To be able to remove the items in the origin list, we do it backwards
    // so that the order of indices stays the same
    positionList = positionList.OrderByDescending(p => p).ToList();
    foreach (var position in positionList)
    {
        messageList.RemoveAt(position);
    }
}

0

In C # können Sie einfach diejenigen markieren, die Sie löschen möchten, und dann eine neue Liste erstellen, über die Sie iterieren können ...

foreach(var item in list.ToList()){if(item.Delete) list.Remove(item);}  

oder noch einfacher linq verwenden ....

list.RemoveAll(p=>p.Delete);

Es lohnt sich jedoch zu überlegen, ob andere Aufgaben oder Threads gleichzeitig mit dem Entfernen Zugriff auf dieselbe Liste haben und stattdessen möglicherweise eine ConcurrentList verwenden.


0

Verfolgen Sie die zu entfernenden Elemente mit einer Eigenschaft und entfernen Sie sie alle nach dem Vorgang.

using System.Linq;

List<MyProperty> _Group = new List<MyProperty>();
// ... add elements

bool cond = true;
foreach (MyProperty currObj in _Group)
{
    if (cond) 
    {
        // SET - element can be deleted
        currObj.REMOVE_ME = true;
    }
}
// RESET
_Group.RemoveAll(r => r.REMOVE_ME);

0

Ich wollte nur meine 2 Cent hinzufügen, falls dies jemandem hilft. Ich hatte ein ähnliches Problem, musste aber mehrere Elemente aus einer Array-Liste entfernen, während es wiederholt wurde. Die am höchsten bewertete Antwort hat dies größtenteils für mich getan, bis ich auf Fehler stieß und feststellte, dass der Index in einigen Fällen größer als die Größe der Array-Liste war, da mehrere Elemente entfernt wurden, der Index der Schleife jedoch nicht beibehalten wurde Spur davon. Ich habe dies mit einem einfachen Check behoben:

ArrayList place_holder = new ArrayList();
place_holder.Add("1");
place_holder.Add("2");
place_holder.Add("3");
place_holder.Add("4");

for(int i = place_holder.Count-1; i>= 0; i--){
    if(i>= place_holder.Count){
        i = place_holder.Count-1; 
    }

// some method that removes multiple elements here
}

-4
myList.RemoveAt(i--);

simples;

1
Was macht simples;hier?
Denny

6
Es ist ein Delegierter. Es stimmt meine Antwort jedes Mal ab, wenn es läuft
SW

SW ROFL! Ich muss deinen Kommentar lieben.
Erick Brown
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.