Sammlung wurde geändert; Aufzählungsoperation wird möglicherweise nicht ausgeführt


911

Ich kann diesem Fehler nicht auf den Grund gehen, denn wenn der Debugger angehängt ist, scheint er nicht aufzutreten. Unten ist der Code.

Dies ist ein WCF-Server in einem Windows-Dienst. Die Methode NotifySubscribers wird vom Dienst immer dann aufgerufen, wenn ein Datenereignis vorliegt (in zufälligen Intervallen, aber nicht sehr oft - ungefähr 800 Mal pro Tag).

Wenn ein Windows Forms-Client abonniert, wird die Abonnenten-ID zum Abonnentenwörterbuch hinzugefügt, und wenn der Client sich abmeldet, wird sie aus dem Wörterbuch gelöscht. Der Fehler tritt auf, wenn (oder nachdem) sich ein Client abmeldet. Es scheint, dass beim nächsten Aufruf der NotifySubscribers () -Methode die foreach () -Schleife mit dem Fehler in der Betreffzeile fehlschlägt. Die Methode schreibt den Fehler wie im folgenden Code gezeigt in das Anwendungsprotokoll. Wenn ein Debugger angehängt ist und ein Client sich abmeldet, wird der Code ordnungsgemäß ausgeführt.

Sehen Sie ein Problem mit diesem Code? Muss ich das Wörterbuch threadsicher machen?

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class SubscriptionServer : ISubscriptionServer
{
    private static IDictionary<Guid, Subscriber> subscribers;

    public SubscriptionServer()
    {            
        subscribers = new Dictionary<Guid, Subscriber>();
    }

    public void NotifySubscribers(DataRecord sr)
    {
        foreach(Subscriber s in subscribers.Values)
        {
            try
            {
                s.Callback.SignalData(sr);
            }
            catch (Exception e)
            {
                DCS.WriteToApplicationLog(e.Message, 
                  System.Diagnostics.EventLogEntryType.Error);

                UnsubscribeEvent(s.ClientId);
            }
        }
    }


    public Guid SubscribeEvent(string clientDescription)
    {
        Subscriber subscriber = new Subscriber();
        subscriber.Callback = OperationContext.Current.
                GetCallbackChannel<IDCSCallback>();

        subscribers.Add(subscriber.ClientId, subscriber);

        return subscriber.ClientId;
    }


    public void UnsubscribeEvent(Guid clientId)
    {
        try
        {
            subscribers.Remove(clientId);
        }
        catch(Exception e)
        {
            System.Diagnostics.Debug.WriteLine("Unsubscribe Error " + 
                    e.Message);
        }
    }
}

In meinem Fall war es ein Sicherheiteneffekt, weil ich einige .Include ("Tabelle") verwendet habe, die während des Prozesses geändert wurden - nicht sehr offensichtlich beim Lesen des Codes. Ich hatte jedoch das Glück, dass diese Includes nicht benötigt wurden (ja! alter, nicht gepflegter Code) und ich habe mein Problem gelöst, indem ich sie einfach entfernt habe
Adi

Antworten:


1631

Was wahrscheinlich passiert, ist, dass SignalData während der Schleife indirekt das Abonnentenwörterbuch unter der Haube ändert und zu dieser Nachricht führt. Sie können dies durch Ändern überprüfen

foreach(Subscriber s in subscribers.Values)

Zu

foreach(Subscriber s in subscribers.Values.ToList())

Wenn ich recht habe, wird das Problem verschwinden

Beim Aufrufen werden subscribers.Values.ToList()die Werte von subscribers.Valuesin eine separate Liste am Anfang des kopiert foreach. Nichts anderes hat Zugriff auf diese Liste (sie hat nicht einmal einen Variablennamen!), Daher kann nichts sie innerhalb der Schleife ändern.


14
BTW .ToList () ist in der System.Core-DLL vorhanden, die nicht mit .NET 2.0-Anwendungen kompatibel ist. Daher müssen Sie möglicherweise Ihre Ziel-App auf .Net 3.5
ändern

60
Ich verstehe nicht, warum Sie eine ToList erstellt haben und warum dies alles behebt
PositiveGuy

204
@CoffeeAddict: Das Problem ist, dass subscribers.Valueses innerhalb der foreachSchleife geändert wird. Beim Aufrufen werden subscribers.Values.ToList()die Werte von subscribers.Valuesin eine separate Liste am Anfang des kopiert foreach. Nichts anderes hat Zugriff auf diese Liste (sie hat nicht einmal einen Variablennamen!) , Daher kann nichts sie innerhalb der Schleife ändern.
BlueRaja - Danny Pflughoeft

31
Beachten Sie, dass ToListdies auch ausgelöst werden kann, wenn die Sammlung während der ToListAusführung geändert wurde .
Sriram Sakthivel

16
Ich bin mir ziemlich sicher, dass Think das Problem nicht behebt, sondern nur die Reproduktion erschwert. ToListist keine atomare Operation. Was noch lustiger ist, ToListmacht sich foreachintern grundsätzlich selbstständig, um Elemente in eine neue Listeninstanz zu kopieren. Dies bedeutet , dass Sie ein foreachProblem behoben haben , indem Sie eine zusätzliche (wenn auch schnellere) foreachIteration hinzugefügt haben .
Groo

113

Wenn sich ein Abonnent abmeldet, ändern Sie während der Aufzählung den Inhalt der Abonnentensammlung.

Es gibt verschiedene Möglichkeiten, dies zu beheben. Eine davon besteht darin, die for-Schleife so zu ändern, dass eine explizite verwendet wird .ToList():

public void NotifySubscribers(DataRecord sr)  
{
    foreach(Subscriber s in subscribers.Values.ToList())
    {
                                              ^^^^^^^^^  
        ...

64

Meiner Meinung nach ist es effizienter, eine andere Liste zu erstellen, in die Sie alles einfügen, was "entfernt" werden soll. Nachdem Sie Ihre Hauptschleife beendet haben (ohne .ToList ()), führen Sie eine weitere Schleife über die Liste "Zu entfernen" durch und entfernen jeden Eintrag, sobald dies geschieht. Also fügst du in deiner Klasse hinzu:

private List<Guid> toBeRemoved = new List<Guid>();

Dann ändern Sie es in:

public void NotifySubscribers(DataRecord sr)
{
    toBeRemoved.Clear();

    ...your unchanged code skipped...

   foreach ( Guid clientId in toBeRemoved )
   {
        try
        {
            subscribers.Remove(clientId);
        }
        catch(Exception e)
        {
            System.Diagnostics.Debug.WriteLine("Unsubscribe Error " + 
                e.Message);
        }
   }
}

...your unchanged code skipped...

public void UnsubscribeEvent(Guid clientId)
{
    toBeRemoved.Add( clientId );
}

Dies löst nicht nur Ihr Problem, sondern verhindert auch, dass Sie ständig eine Liste aus Ihrem Wörterbuch erstellen müssen. Dies ist teuer, wenn sich viele Abonnenten dort befinden. Angenommen, die Liste der Abonnenten, die bei einer bestimmten Iteration entfernt werden sollen, ist niedriger als die Gesamtzahl in der Liste. Dies sollte schneller sein. Aber natürlich können Sie es auch profilieren, um sicherzugehen, dass dies der Fall ist, wenn Zweifel an Ihrer spezifischen Verwendungssituation bestehen.


9
Ich erwarte, dass dies eine Überlegung wert ist, wenn Sie eine haben, mit der Sie mit größeren Sammlungen arbeiten. Wenn es klein wäre, würde ich wahrscheinlich nur ToList und weitermachen.
Karl Kieninger

42

Sie können das Abonnentenwörterbuch auch sperren, um zu verhindern, dass es bei jeder Schleife geändert wird:

 lock (subscribers)
 {
         foreach (var subscriber in subscribers)
         {
               //do something
         }
 }

2
Ist das ein vollständiges Beispiel? Ich habe eine Klasse (_dictionary obj unten), die ein generisches Dictionary <string, int> mit dem Namen MarkerFrequitudes enthält, aber dies hat den Absturz nicht sofort gelöst: lock (_dictionary.MarkerFrequitudes) {foreach (KeyValuePair <string, int> pair in _dictionary.MarkerFrequitudes) {...}}
Jon Coombs

4
@JCoombs Es ist möglich, dass Sie das MarkerFrequenciesWörterbuch innerhalb der Sperre selbst ändern und möglicherweise neu zuweisen , was bedeutet, dass die ursprüngliche Instanz nicht mehr gesperrt ist. Versuchen Sie auch, a foranstelle von a zu verwenden foreach. Siehe dies und das . Lassen Sie mich wissen, ob das das Problem löst.
Mohammad Sepahvand

1
Nun, ich habe es endlich geschafft, diesen Fehler zu beheben, und diese Tipps waren hilfreich - danke! Mein Datensatz war klein, und dieser Vorgang war nur ein Update der Benutzeroberfläche, sodass das Durchlaufen einer Kopie am besten schien. (Ich habe auch mit der Verwendung von for anstelle von foreach experimentiert, nachdem MarkerFrequences in beiden Threads gesperrt wurden. Dies verhinderte den Absturz, schien jedoch weitere Debugging-Arbeiten zu erfordern. Dies führte zu einer höheren Komplexität. Mein einziger Grund für zwei Threads besteht darin, dem Benutzer das Abbrechen zu ermöglichen Übrigens eine Operation. Die Benutzeroberfläche ändert diese Daten nicht direkt.)
Jon Coombs

9
Das Problem ist, dass Sperren für große Anwendungen ein großer Leistungseinbruch sein können - besser geeignet, um eine Sammlung im System.Collections.ConcurrentNamespace zu verwenden.
BrainSlugs83

28

Warum dieser Fehler?

Im Allgemeinen unterstützen .Net-Sammlungen nicht die gleichzeitige Aufzählung und Änderung. Wenn Sie versuchen, die Auflistungsliste während der Aufzählung zu ändern, wird eine Ausnahme ausgelöst. Das Problem hinter diesem Fehler ist also, dass wir die Liste / das Wörterbuch nicht ändern können, während wir dieselbe durchlaufen.

Eine der Lösungen

Wenn wir ein Wörterbuch anhand einer Liste seiner Schlüssel iterieren, können wir parallel das Wörterbuchobjekt ändern, während wir die Schlüsselsammlung und nicht das Wörterbuch durchlaufen (und seine Schlüsselsammlung iterieren).

Beispiel

//get key collection from dictionary into a list to loop through
List<int> keys = new List<int>(Dictionary.Keys);

// iterating key collection using a simple for-each loop
foreach (int key in keys)
{
  // Now we can perform any modification with values of the dictionary.
  Dictionary[key] = Dictionary[key] - 1;
}

Hier ist ein Blog-Beitrag über diese Lösung.

Und für einen tiefen Tauchgang in StackOverflow: Warum tritt dieser Fehler auf?


Vielen Dank! Eine klare Erklärung, warum es passiert und gab mir sofort die Möglichkeit, dies in meiner Bewerbung zu lösen.
Kim Crosser

5

Eigentlich scheint mir das Problem, dass Sie Elemente aus der Liste entfernen und erwarten, die Liste weiterhin zu lesen, als wäre nichts passiert.

Was Sie wirklich tun müssen, ist, vom Ende bis zum Anfang zu beginnen. Selbst wenn Sie Elemente aus der Liste entfernen, können Sie sie weiter lesen.


Ich sehe nicht, wie das einen Unterschied machen würde? Wenn Sie ein Element in der Mitte der Liste entfernen, wird dennoch ein Fehler ausgegeben, da das Element, auf das zugegriffen werden soll, nicht gefunden werden kann.
Zapnologica

4
@Zapnologica der Unterschied ist - Sie würden die Liste nicht aufzählen - anstatt ein für / jedes zu machen, würden Sie ein für / next machen und über eine Ganzzahl darauf zugreifen - Sie können definitiv eine Liste a in a ändern for / next-Schleife, aber niemals in einer for / each-Schleife (weil for / each auflistet ) - Sie können dies auch in einer for / next- Schleife tun, vorausgesetzt, Sie haben zusätzliche Logik zum Anpassen Ihrer Zähler usw.
BrainSlugs83

4

InvalidOperationException - Eine InvalidOperationException ist aufgetreten. Es wird berichtet, dass eine "Sammlung geändert wurde" in einer foreach-Schleife

Verwenden Sie die break-Anweisung, sobald das Objekt entfernt wurde.

Ex:

ArrayList list = new ArrayList(); 

foreach (var item in list)
{
    if(condition)
    {
        list.remove(item);
        break;
    }
}

Gute und einfache Lösung, wenn Sie wissen, dass in Ihrer Liste höchstens ein Element entfernt werden muss.
Tawab Wakil

3

Ich hatte das gleiche Problem und es wurde gelöst, als ich forstattdessen eine Schleife verwendete foreach.

// foreach (var item in itemsToBeLast)
for (int i = 0; i < itemsToBeLast.Count; i++)
{
    var matchingItem = itemsToBeLast.FirstOrDefault(item => item.Detach);

   if (matchingItem != null)
   {
      itemsToBeLast.Remove(matchingItem);
      continue;
   }
   allItems.Add(itemsToBeLast[i]);// (attachDetachItem);
}

11
Dieser Code ist falsch und überspringt einige Elemente in der Sammlung, wenn ein Element entfernt wird. Beispiel: Sie haben var arr = ["a", "b", "c"] und entfernen in der ersten Iteration (i = 0) das Element an Position 0 (Element "a"). Danach bewegen sich alle Array-Elemente um eine Position nach oben und das Array ist ["b", "c"]. In der nächsten Iteration (i = 1) überprüfen Sie das Element an Position 1, das "c" und nicht "b" ist. Das ist falsch. Um das zu beheben, müssen Sie von unten nach oben bewegen
Kaspars Ozols

3

Okay, was mir geholfen hat, war rückwärts zu iterieren. Ich habe versucht, einen Eintrag aus einer Liste zu entfernen, aber nach oben iteriert und die Schleife durcheinander gebracht, weil der Eintrag nicht mehr vorhanden war:

for (int x = myList.Count - 1; x > -1; x--)
                        {

                            myList.RemoveAt(x);

                        }

2

Ich habe viele Optionen dafür gesehen, aber für mich war diese die beste.

ListItemCollection collection = new ListItemCollection();
        foreach (ListItem item in ListBox1.Items)
        {
            if (item.Selected)
                collection.Add(item);
        }

Dann durchlaufen Sie einfach die Sammlung.

Beachten Sie, dass eine ListItemCollection Duplikate enthalten kann. Standardmäßig verhindert nichts, dass der Sammlung Duplikate hinzugefügt werden. Um Duplikate zu vermeiden, können Sie Folgendes tun:

ListItemCollection collection = new ListItemCollection();
            foreach (ListItem item in ListBox1.Items)
            {
                if (item.Selected && !collection.Contains(item))
                    collection.Add(item);
            }

Wie würde dieser Code verhindern, dass Duplikate in die Datenbank eingegeben werden? Ich habe etwas Ähnliches geschrieben. Wenn ich neue Benutzer aus der Listbox hinzufüge und versehentlich einen auswähle, der bereits in der Liste enthalten ist, wird ein doppelter Eintrag erstellt. Hast du irgendwelche Vorschläge @Mike?
Jamie

1

Die akzeptierte Antwort ist ungenau und im schlimmsten Fall falsch. Wenn während Änderungen vorgenommen werdenToList() , kann dennoch ein Fehler auftreten. Außerdem lock, welche Leistung und Thread-Sicherheit berücksichtigt werden muss, wenn Sie ein öffentliches Mitglied haben, kann eine geeignete Lösung die Verwendung unveränderlicher Typen sein .

Im Allgemeinen bedeutet ein unveränderlicher Typ, dass Sie den einmal erstellten Status nicht ändern können. Ihr Code sollte also so aussehen:

public class SubscriptionServer : ISubscriptionServer
{
    private static ImmutableDictionary<Guid, Subscriber> subscribers = ImmutableDictionary<Guid, Subscriber>.Empty;
    public void SubscribeEvent(string id)
    {
        subscribers = subscribers.Add(Guid.NewGuid(), new Subscriber());
    }
    public void NotifyEvent()
    {
        foreach(var sub in subscribers.Values)
        {
            //.....This is always safe
        }
    }
    //.........
}

Dies kann besonders nützlich sein, wenn Sie ein öffentliches Mitglied haben. Andere Klassen können immer foreachunveränderliche Typen verwenden, ohne sich Gedanken über die Änderung der Sammlung machen zu müssen.


1

Hier ist ein spezifisches Szenario, das einen speziellen Ansatz erfordert:

  1. Das Dictionarywird häufig aufgezählt.
  2. Das Dictionarywird selten geändert.

In diesem Szenario kann das Erstellen einer Kopie des Dictionary(oder des Dictionary.Values) vor jeder Aufzählung sehr kostspielig sein. Meine Idee zur Lösung dieses Problems ist es, dieselbe zwischengespeicherte Kopie in mehreren Aufzählungen wiederzuverwenden und ein IEnumeratorOriginal Dictionaryauf Ausnahmen zu prüfen. Der Enumerator wird zusammen mit den kopierten Daten zwischengespeichert und abgefragt, bevor eine neue Enumeration gestartet wird. Im Falle einer Ausnahme wird die zwischengespeicherte Kopie verworfen und eine neue erstellt. Hier ist meine Umsetzung dieser Idee:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

public class EnumerableSnapshot<T> : IEnumerable<T>, IDisposable
{
    private IEnumerable<T> _source;
    private IEnumerator<T> _enumerator;
    private ReadOnlyCollection<T> _cached;

    public EnumerableSnapshot(IEnumerable<T> source)
    {
        _source = source ?? throw new ArgumentNullException(nameof(source));
    }

    public IEnumerator<T> GetEnumerator()
    {
        if (_source == null) throw new ObjectDisposedException(this.GetType().Name);
        if (_enumerator == null)
        {
            _enumerator = _source.GetEnumerator();
            _cached = new ReadOnlyCollection<T>(_source.ToArray());
        }
        else
        {
            var modified = false;
            if (_source is ICollection collection) // C# 7 syntax
            {
                modified = _cached.Count != collection.Count;
            }
            if (!modified)
            {
                try
                {
                    _enumerator.MoveNext();
                }
                catch (InvalidOperationException)
                {
                    modified = true;
                }
            }
            if (modified)
            {
                _enumerator.Dispose();
                _enumerator = _source.GetEnumerator();
                _cached = new ReadOnlyCollection<T>(_source.ToArray());
            }
        }
        return _cached.GetEnumerator();
    }

    public void Dispose()
    {
        _enumerator?.Dispose();
        _enumerator = null;
        _cached = null;
        _source = null;
    }

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

public static class EnumerableSnapshotExtensions
{
    public static EnumerableSnapshot<T> ToEnumerableSnapshot<T>(
        this IEnumerable<T> source) => new EnumerableSnapshot<T>(source);
}

Anwendungsbeispiel:

private static IDictionary<Guid, Subscriber> _subscribers;
private static EnumerableSnapshot<Subscriber> _subscribersSnapshot;

//...(in the constructor)
_subscribers = new Dictionary<Guid, Subscriber>();
_subscribersSnapshot = _subscribers.Values.ToEnumerableSnapshot();

// ...(elsewere)
foreach (var subscriber in _subscribersSnapshot)
{
    //...
}

Leider kann diese Idee derzeit nicht mit der Klasse Dictionaryin .NET Core 3.0 verwendet werden, da diese Klasse keine Ausnahme auslöst, wenn die Auflistung geändert wurde und die Methoden Removeund Clearaufgerufen werden. Alle anderen von mir überprüften Container verhalten sich konsistent. Ich habe systematisch diese Klassen: List<T>, Collection<T>, ObservableCollection<T>, HashSet<T>, SortedSet<T>, Dictionary<T,V>und SortedDictionary<T,V>. Nur die beiden oben genannten Methoden der DictionaryKlasse in .NET Core machen die Aufzählung nicht ungültig.


Update: Ich habe das obige Problem behoben, indem ich auch die Längen der zwischengespeicherten und der ursprünglichen Sammlung verglichen habe. Bei diesem Fix wird davon ausgegangen, dass das Wörterbuch direkt als Argument an den EnumerableSnapshotKonstruktor des 'übergeben wird und seine Identität nicht durch (zum Beispiel) eine Projektion wie: verborgen wird dictionary.Select(e => e).ΤοEnumerableSnapshot().


Wichtig: Die obige Klasse ist nicht threadsicher. Es soll aus Code verwendet werden, der ausschließlich in einem einzelnen Thread ausgeführt wird.


0

Sie können das Abonnentenwörterbuchobjekt in denselben Typ eines temporären Wörterbuchobjekts kopieren und dann das temporäre Wörterbuchobjekt mithilfe der foreach-Schleife iterieren.


(Dieser Beitrag scheint keine qualitativ hochwertige Antwort auf die Frage zu bieten . Bitte bearbeiten Sie entweder Ihre Antwort und oder posten Sie sie einfach als Kommentar zur Frage.)
sɐunıɔ ןɐ qɐp

0

Eine andere Möglichkeit, dieses Problem zu lösen, besteht darin, anstelle des Entfernens der Elemente ein neues Wörterbuch zu erstellen und nur die Elemente hinzuzufügen, die Sie nicht entfernen möchten, und dann das ursprüngliche Wörterbuch durch das neue zu ersetzen. Ich denke nicht, dass dies ein zu großes Effizienzproblem ist, da es nicht die Häufigkeit erhöht, mit der Sie über die Struktur iterieren.


0

Es gibt einen Link, bei dem es sehr gut ausgearbeitet wurde und es wird auch eine Lösung angegeben. Probieren Sie es aus, wenn Sie die richtige Lösung gefunden haben. Bitte posten Sie hier, damit andere verstehen können. Die gegebene Lösung ist dann wie in der Post in Ordnung, damit andere diese Lösung ausprobieren können.

für Sie Referenz Original Link: - https://bensonxion.wordpress.com/2012/05/07/serializing-an-ienumerable-produces-collection-was-modified-enumeration-operation-may-not-execute/

Wenn wir .Net-Serialisierungsklassen verwenden, um ein Objekt zu serialisieren, dessen Definition einen Enumerable-Typ enthält, dh eine Auflistung, wird leicht die InvalidOperationException mit der Meldung "Sammlung wurde geändert; Aufzählungsoperation wird möglicherweise nicht ausgeführt" angezeigt, wenn sich Ihre Codierung in Multithread-Szenarien befindet. Die unterste Ursache ist, dass Serialisierungsklassen die Sammlung über den Enumerator durchlaufen. Daher besteht das Problem darin, dass versucht wird, eine Sammlung zu durchlaufen, während sie geändert wird.

Erste Lösung: Wir können lock einfach als Synchronisationslösung verwenden, um sicherzustellen, dass die Operation für das List-Objekt jeweils nur von einem Thread ausgeführt werden kann. Offensichtlich erhalten Sie eine Leistungsbeeinträchtigung: Wenn Sie eine Sammlung dieses Objekts serialisieren möchten, wird für jedes Objekt die Sperre angewendet.

Nun, .NET 4.0, das den Umgang mit Multithreading-Szenarien praktisch macht. Bei diesem Problem mit dem Serialisierungssammlungsfeld habe ich festgestellt, dass wir nur von der ConcurrentQueue-Klasse (Check MSDN) profitieren können, die eine thread-sichere und FIFO-Sammlung ist und Code sperrfrei macht.

Wenn Sie diese Klasse verwenden, ersetzen Sie in ihrer Einfachheit die Elemente, die Sie für Ihren Code ändern müssen, durch den Sammlungstyp. Fügen Sie mit Enqueue ein Element am Ende von ConcurrentQueue hinzu und entfernen Sie diesen Sperrcode. Wenn für das Szenario, an dem Sie arbeiten, Erfassungssachen wie List erforderlich sind, benötigen Sie ein paar weitere Codes, um ConcurrentQueue an Ihre Felder anzupassen.

Übrigens hat ConcurrentQueue aufgrund des zugrunde liegenden Algorithmus, der kein atomares Löschen der Sammlung erlaubt, keine Clear-Methode. Sie müssen es also selbst tun. Der schnellste Weg besteht darin, eine neue leere ConcurrentQueue für einen Ersatz neu zu erstellen.


-1

Ich persönlich bin kürzlich in unbekanntem Code darauf gestoßen, wo es sich in einem offenen Datenbankkontext mit Linqs .Remove (item) befand. Ich hatte verdammt viel Zeit damit, das Fehler-Debugging zu finden, weil die Elemente beim ersten Iterieren entfernt wurden. Wenn ich versuchte, einen Schritt zurückzutreten und erneut einen Schritt zurückzutreten, war die Sammlung leer, da ich mich im selben Kontext befand!

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.