Problem beim Löschen/Hinzufügen von Objekten in einer Liste



  • Hallo allerseits,

    ich habe mal eine Nachfrage zum Thema Liste.

    Zum Hintergrund:

    class dbBeobachter {
    
    int dbBeobachter_id;
    String ^dbBeobName;
    
    }
    

    Diese Klasse nutze ich als Typ einer Liste:

    List<dbBeobachter ^>
    

    Diese Liste ist Eigenschaft einer Klasse. Hintergrund ist, dass die Klasse dbBeobachter eine Datenbank Tabelle abbildet. Diese möchte ich also in der Klasse BeobachterList in eine Liste einlesen:

    class BeobachterList {
    
    List<dbBeobacher ^> ^beobList;
    
    }
    

    In der Klasse BeobachterList habe ich nun mehrere Methoden zum ändern, löschen und einfügen von Beobachtern. Der Konstruktor liest dabei die Daten einmalig in die Liste ein. Die Methoden zum ändern, löschen und anfügen arbeiten erst die DB ab und dann die Liste.

    Im Prinzip funktioniert das auch. Nur macht er Probleme, wenn ich Elemente aus der Liste mittels ->Remove(obj) entferne. Da kommt er mit dem Enumerator wohl durcheinander. Beim Debuggen habe ich gesehen, dass zwar das Objekt entfernt wurde, aber der Platz nicht freigegeben wurde. Beim erneuten Zugreifen hat er dann eine Exception ausgeworden:

    Eine Ausnahme (erste Chance) des Typs "System.InvalidOperationException" ist in mscorlib.dll aufgetreten.
    Eine nicht behandelte Ausnahme des Typs "System.InvalidOperationException" ist in mscorlib.dll aufgetreten.

    Zusätzliche Informationen: Die Auflistung wurde geändert. Der Enumerationsvorgang kann möglicherweise nicht ausgeführt werden.

    Meine Frage: Ist das jetzt ein Programmierfehler bei mir oder kann man mit Listen so wie ich es möchte nicht arbeiten?

    Alternative wäre aus meiner Sicht, bei jeder Änderung die Liste zu kopieren in eine andere Liste, aber das ist suboptimal.

    LG Solick



  • Habe ich richtig verstanden, dass du beim Enumerieren Elemente hinzufügen und/oder löschen willst? Wenn ja, muss ich dich wohl leider enttäuschen: Enumeratoren dürfen bei der Veränderung der Liste selbst ungültig werden. Wenn du beim Enumerieren Elemente nach bestimmten Kriterien aussortieren und löschen willst, dann benutze besser die Methode RemoveAll(Predicate<T> ^) ; da erübrigt sich der Enumerator dann auch...

    MfG



  • die Löschroutine sieht so aus:

    for each(dbBeobachter ^b in beobList)
    				{
    					if(b->Beobachter::get() == delName) 
    					{
    						beobList->Remove(b);
    					}
    				}
    

    sollte ich also besser:

    beobList->RemoveAll(b);
    

    verwenden?



  • solick schrieb:

    die Löschroutine sieht so aus:

    for each(dbBeobachter ^b in beobList)
    				{
    					if(b->Beobachter::get() == delName) 
    					{
    						beobList->Remove(b);
    					}
    				}
    

    sollte ich also besser:

    beobList->RemoveAll(b);
    

    verwenden?

    Du kannst nicht in einem for-each Elemente löschen oder hinzufügen; RemoveAll mit if (b->Beobachter::get() == delName) als einem Predicate<dbBeobachter ^> ^ ist vermutlich genau, was du suchst.



  • hmm ich hab versucht das Predicate nachzulesen, bin aber nicht ganz schlau daraus geworden. Was macht das genau?

    Aber mal grundsätzlich: Man kann doch aus einer Liste elemente löschen und hinzufügen. Wie verhält sich da der Enumerator?



  • solick schrieb:

    Aber mal grundsätzlich: Man kann doch aus einer Liste elemente löschen und hinzufügen. Wie verhält sich da der Enumerator?

    Wie gesagt, der Enumerator wird ungültig. Das Verhalten kennt man doch auch von den Iteratoren schon...

    solick schrieb:

    hmm ich hab versucht das Predicate nachzulesen, bin aber nicht ganz schlau daraus geworden. Was macht das genau?

    Ein Predicate<T> ist das Äquivalent zu einem Funktionszeiger bool (*)(T) ; ein solches Predicate kannst du mit RemoveAll zu benutzen, um alle Elemente durchzugehen, und diejenigen, für welche das Predicate true zurückgibt, entfernen.

    generic<typename T>
    bool p(T v)
    {
        // ...
        return false;
    }
    .
    .
    .
    Predicate<dbBeobachter ^> ^f = gcnew Predicate<dbBeobachter ^>(&p);
    // ...
    beobList->RemoveAll(f); // Alle Elemente i mit f(i)==true entfernen...
    

    MfG



  • ok vielen Dank erstmal für die Erläuterungen, werds mal versuchen umzusetzen.

    Es bedeutet aber, dass ich trotz Einsatz von Predicate nach einem löschen oder hinzufügen keine for each -Schleifen mehr auf die Liste anwenden kann, richtig?

    Also sollte ich wohl in meinem Code generell besser via Predicate auf Listen-Elemente zugreifen, oder?



  • solick schrieb:

    Es bedeutet aber, dass ich trotz Einsatz von Predicate nach einem löschen oder hinzufügen keine for each -Schleifen mehr auf die Liste anwenden kann, richtig?

    Nein. Es bedeutet nur, dass Du *während* Du in einer foreach-Schleife bist, Du die Liste nicht ändern darfst!



  • Ah ok, jetzt hab ichs verstanden. Ist ja auch logisch, er kann ja dann die Liste nicht mehr durchlaufen... manchmal sieht man den Wald vor lauter Bäumen nicht mehr...

    Danke nochmal !!

    Lg Solick



  • Hallo,

    ich hab da doch noch mal ne Frage 😉

    Die meisten Erläuerungen zu Predicates gehen dahin, dass man in der Klasse den ()-Operator überläd... Dein Code implementiert das aber nur als normale Funktion oder?

    Was ist denn jetzt besser / einfacher?

    Gruß Solick



  • solick schrieb:

    Hallo,

    ich hab da doch noch mal ne Frage 😉

    Die meisten Erläuerungen zu Predicates gehen dahin, dass man in der Klasse den ()-Operator überläd... Dein Code implementiert das aber nur als normale Funktion oder?

    Was ist denn jetzt besser / einfacher?

    Gruß Solick

    Nun, dass ist die .NET-Welt, in der du dich hier bewegst. Ein Predicate, welches du mit der Funktion RemoveAll verwenden kannst, musst du mit einem Delegate der Form

    generic<typename T>
    public delegate bool Predicate(T);
    

    ansprechen können. Ob es sich dabei intern um einen überladenen operator() handelt, oder eine Memberfunktion oder was auch immer, spielt keine Rolle. Möglich sind freilich beide Varianten. Am einfachsten wird es wohl sein, eine normale Funktion zu benutzen^^

    MfG



  • Also ich komm einfach nicht weiter:

    Predicate<acfunc::dbBeobachter ^> ^f = gcnew Predicate<acfunc::dbBeobachter ^>(&p);
    
    				for each(dbBeobachter ^b in beobList)
    				{
    					//if(b->Beobachter::get() == delName) 
    					//{
    						beobList->RemoveAll(f);
    					//}
    				}
    

    Und dann versuche ich die Funktion zu implementieren aber bekomme da nicht den Durchblick:

    generic <typename T> bool p(T v, T s)
    				{
    					if(v == s)
    					{
    						return true;
    					}
    					else
    					{
    						return false;
    					}
    
    				}
    

    Ich hab die Funktion jetzt ausserhalb der verwendenden Methode aber innerhalb der Klasse implementiert, da ich sie noch für andere Methoden bräuchte (wenns denn mal gehen würde)

    Leider ist mir in keiner Weise klar, wie ich den Vergleich implementieren soll.

    Hab diverses ausprobiert, aber ich versteh dieses Konstrukt einfach nicht. Alle beispiele sind irgendwie anders die ich finde...

    Könnt Ihr mir bitte noch einen Hinweis geben, ich steh gerade total auf dem Schlauch...

    Wieso kann ich nicht einfach eine Funktion schreiben, der ich zwei Strings übergebe und dann bei Gleichheit true, sonst false zurückgebe... Wieso muss es mit dieser Predicate Geschichte gehen?

    Lg Solick



  • solick schrieb:

    Also ich komm einfach nicht weiter:

    Predicate<acfunc::dbBeobachter ^> ^f = gcnew Predicate<acfunc::dbBeobachter ^>(&p);
    
    				for each(dbBeobachter ^b in beobList)
    				{
    					//if(b->Beobachter::get() == delName) 
    					//{
    						beobList->RemoveAll(f);
    					//}
    				}
    

    Das for each brauchst du nicht mehr, es steckt ja bereits in RemoveAll verborgen drin... das Predicate delegate wurde als generic implementiert, um die generische Verwendung zu ermöglichen. In deinem Fall brauchst du einfach eine normale Funktion vom Typ bool(dbBeobachter ^) :

    bool p(dbBeobachter ^v)
    {
        // return ... vergleich^^
    }
    

    Was die Funktion betrifft, so musst du sie selbst natürlich nicht als generic implementieren (du kannst, aber dann wird es unnötig kompliziert und sinnlos^^). Wenn du nun mit einem bestimmmten Variablen Wert vergleichen willst, so musst du dieses Predicate als Memberfunktion realisieren, und der Instanz der Klasse den Vergleichswert übergeben. Und dann ein Instanz-Member Delegate bauen...

    Wieso kann ich nicht einfach eine Funktion schreiben, der ich zwei Strings übergebe und dann bei Gleichheit true, sonst false zurückgebe... Wieso muss es mit dieser Predicate Geschichte gehen?

    Das kommt daher zustande, dass die anderen .NET Sprachen (insbesondere C#, mit welchem die Bibliotheken hauptsächlich geschrieben wurden) mehrheitlich keine Functionpointer kennen. Die Delegates sind das .NET Gegenstück dazu.

    MfG 🙂



  • So, soweit bin ich bisher gekommen:

    Aufruf innerhalb der Methode delName():

    beobList->RemoveAll(gcnew Predicate<acfunc::dbBeobachter ^>(&acfunc::BeobachterList::p,delName));
    

    Predicate-Methode p:

    bool BeobachterList::p(acfunc::dbBeobachter ^v,String ^s)
    	{
    		if(beobList->Find(s))
    		{
    			return true;
    		}
    		else
    		{
    			return false;
    		}
    
    	}
    

    Wie ihr seht, versuche ich, den String zu übergeben, der mit einer Eigenschaft des Objektes gematcht werden soll. Das scheint aber irgendwie nicht zu gehen. im unmanaged C++ kann man das wohl mit bind2nd machen, aber im managed C++ weiss ich es nicht.

    Alternativ könnte ich eine neue Variable in der Klasse einführen, der ich dann den zu matchenden String zu weise, aber das wäre ja nicht wirklich sauber.

    Leider habe ich bisher nur Anleitungen für Predicate - Verwendungen im unmanaged c++ gefunden. Dort würde ich den Operator() überladen und dann den zu matchenden String mit übergeben.

    unter -NET soll man aber explizit ein Predicate-Objekt erzeugen. Wie ich dort den String mit übergeben bekomme habe ich bisher nicht rausgefunden. Gibt es bind2nd() auch für .NET?

    Grüße solick



  • Ev. hilft Dir ein Bsp.:

    using namespace System;
    using namespace System::Collections::Generic;
    
    public ref class Person
    {
    public:
    	Person(String^ name) : _name(name)
    	{
    	}
    
    public:
    	property String^ Name
    	{
    		String^ get()
    		{
    			return _name;
    		}
    	}
    
    private:
    	String^ _name;
    };
    
    public ref class PersonFind
    {
    public:
    	PersonFind(String^ name) : _name(name)
    	{
    	}
    
    public:
    	bool Match(Person^ person)
    	{
    		if (nullptr == person || nullptr == _name)
    		{
    			return false;
    		}
    		else
    		{
    			return person->Name == _name;
    		}
    	}
    
    private:
    	String^ _name;
    };
    
    int main(array<System::String ^> ^args)
    {
    	List<Person^>^ list = gcnew List<Person^>();
    	list->Add(gcnew Person(L"Jack"));
    	list->Add(gcnew Person(L"John"));
    	list->Add(gcnew Person(L"Marc"));
    	list->Add(gcnew Person(L"Jack"));
    	list->Add(gcnew Person(L"Jack"));
    	list->Add(gcnew Person(L"Jack"));
    
    	Predicate<Person^>^ predicate = gcnew Predicate<Person^>(gcnew PersonFind(L"Jack"), &PersonFind::Match);
    	list->RemoveAll(predicate);
    
        return 0;
    }
    

    Simon



  • Danke für das Beispiel. Ich wollte dafür eigentlich keine neue Klasse implementieren, aber damit es überhaupt mal funktioniert ebend so.

    Leider gibt mir der Compiler eine recht seltsame Fehlermeldung. Habs wie folgt implementiert:

    Befehl innerhalb der Methode delName:

    Predicate<dbBeobachter ^> ^f = gcnew Predicate<dbBeobachter ^>(gcnew BeobacherFind(delName),&BeobachterFind::Match);
    
    				beobList->RemoveAll(f);
    

    Methode der Klasse BeobacherFind:

    bool BeobachterFind::Match(acfunc::dbBeobachter ^b)
    	{
    		if(b->Beobachter::get() == _name)
    		{
    			return true;
    		}
    		else
    		{
    			return false;
    		}
    
    	}
    

    Der Kompiler meckert bei der Instantierung der BeobachterFind mit delName als übergeber. Da stört er sich an der dereferenzierung auf die Methode Match.

    1>.\BeobachterList.cpp(207) : error C2061: Syntaxfehler: Bezeichner 'BeobacherFind'
    1>.\BeobachterList.cpp(207) : error C3350: "System::Predicate<T>": Ein Delegatkonstruktor erwartet 2 Argument(e).
    1> with
    1> [
    1> T=acfunc::dbBeobachter ^
    1> ]
    1>.\BeobachterList.cpp(207) : error C3699: "&": Diese Referenzierung kann nicht für den Typ "System::Predicate<T>" verwendet werden.
    1> with
    1> [
    1> T=acfunc::dbBeobachter ^
    1> ]
    1> Der Compiler ersetzt "&" durch ^", um die Analyse fortzusetzen.
    1>.\BeobachterList.cpp(207) : error C2761: 'bool acfunc::BeobachterFind::Match(acfunc::dbBeobachter ^)': Die erneute Deklaration der Memberfunktion ist unzulässig
    1>.\BeobachterList.cpp(207) : error C2143: Syntaxfehler: Es fehlt ';' vor ')'
    1>.\BeobachterList.cpp(207) : error C2143: Syntaxfehler: Es fehlt ';' vor ')'

    LG Solick



  • Ist BeobachterFind im BeobachterList.cpp bekannt?
    Inkludes gemacht?

    Simon



  • Jap.

    #Include "BeobachterFind.h"

    Daher wunder ich mich ja so... trat übrigens auch schon auf, als ich die Klasse noch in BeobachterList integriert hatte... Hab sie dann ausgegliedert hat aber nix geholfen...



  • So habs hinbekommen... war natürlich ein kleiner Schreibfehler, der mir entgangen ist.

    Vielen Dank nochmal Für eure Hilfe!!

    LG Solick


Anmelden zum Antworten