Frage

Es fühlt sich an wie es muss eine halb einfache Lösung für dieses sein, aber ich kann es einfach nicht herausfinden.

Edit: Das obige Beispiel zeigte die Endlosschleife deutlicher, aber das gibt ein bisschen mehr Kontext. Schauen Sie sich die Pre-Edit für einen schnellen Überblick über das Problem.

Die folgenden 2 Klassen repräsentieren die View-Modelle aus dem Model-View-View-Modell (

Andere Tipps

Zurück zu Ihrer ursprünglichen Frage (und Code). Wenn das, was wollen Sie ist eine viel 2-many Beziehung zu haben, die automatisch synchronisiert ist, dann lesen Sie weiter. Der beste Ort für einen komplexen Code zu suchen, die diese Fälle behandelt ist die Quellcode von ORM-Framework und es ist sehr weit verbreitetes Problem für diese Domäne von Werkzeugen. Ich würde den Quellcode nHibernate sehen ( https: //nhibernate.svn .sourceforge.net / svnroot / nhibernate / trunk / nhibernate / ), um zu sehen, wie es implementiert Sammlungen, die 1-N und MN Beziehungen behandeln.

Einfach etwas, das Sie versuchen können, ist Ihre eigene kleine Sammlung Klasse zu erstellen, die sich um das dauert nur. Im Folgenden wird ich Ihre ursprünglichen Wrapper-Klassen entfernt und eine bilist Sammlung hinzugefügt, die mit dem Objekt initialisiert wird (der Besitzer der Sammlung) und den Namen der anderen Seite des Grundstücks synchron zu halten mit (funktioniert nur für MN, sondern 1- N würde einfach hinzufügen). Natürlich würden Sie den Code polieren:

using System.Collections.Generic;

public interface IBiList
{
    // Need this interface only to have a 'generic' way to set the other side
    void Add(object value, bool addOtherSide);
}

public class BiList<T> : List<T>, IBiList
{
    private object owner;
    private string otherSideFieldName;

    public BiList(object owner, string otherSideFieldName) {
        this.owner = owner;
        this.otherSideFieldName = otherSideFieldName;
    }

    public new void Add(T value) {
        // add and set the other side as well
        this.Add(value, true);
    }

    void IBiList.Add(object value, bool addOtherSide) {
        this.Add((T)value, addOtherSide);
    }

    public void Add(T value, bool addOtherSide) {
        // note: may check if already in the list/collection
        if (this.Contains(value))
            return;
        // actuall add the object to the list/collection
        base.Add(value);
        // set the other side
        if (addOtherSide && value != null) {
            System.Reflection.FieldInfo x = value.GetType().GetField(this.otherSideFieldName);
            IBiList otherSide = (IBiList) x.GetValue(value);
            // do not set the other side
            otherSide.Add(this.owner, false);
        }
    }
}

class Foo
{
    public BiList<Bar> MyBars;
    public Foo() {
        MyBars = new BiList<Bar>(this, "MyFoos");
    }
}

class Bar
{
    public BiList<Foo> MyFoos;
    public Bar() {
        MyFoos = new BiList<Foo>(this, "MyBars");
    }
}



public class App
{
    public static void Main()
    {
        System.Console.WriteLine("setting...");

        Foo testFoo = new Foo();
        Bar testBar = new Bar();
        Bar testBar2 = new Bar();
        testFoo.MyBars.Add(testBar);
        testFoo.MyBars.Add(testBar2);
        //testBar.MyFoos.Add(testFoo); // do not set this side, we expect it to be set automatically, but doing so will do no harm
        System.Console.WriteLine("getting foos from Bar...");
        foreach (object x in testBar.MyFoos)
        {
            System.Console.WriteLine("  foo:" + x);
        }
        System.Console.WriteLine("getting baars from Foo...");
        foreach (object x in testFoo.MyBars)
        {
            System.Console.WriteLine("  bar:" + x);
        }
    }
}

Zu allererst DI ist nicht Ihr Problem geht zu lösen, aber eine Sache immer im Zusammenhang mit DI , die Ihr Problem lösen wird ist die Nutzung von a Container (oder ein Kontext mit Lookup-Fähigkeit)

Lösung:

Sie Code nicht in diesen Orten:

var catVM = new CategoryViewModel(cat); //Causes infinite loop
...
var recipeVM = new RecipeViewModel(item); //Causes infinite loop

Das Problem wird durch die Tatsache verursacht, dass Sie den Wrapper (xxxViewModel) für ein Objekt erstellen, auch wenn es bereits vorhanden ist. Statt wieder einen Wrapper für das gleiche Objekt erstellen, müssen Sie für dieses Modell bereits, wenn ein Wrapper um zu überprüfen, und es existiert stattdessen verwenden. So benötigen Sie einen Container Überblick über alle erstellten Objekte zu halten. Ihre Optionen sind:

Option-1: verwenden, um ein einfaches a-la-Fabrik-Muster Ihre Objekte zu schaffen, sondern auch den Überblick behalten:

class CategoryViewModelFactory
{
    // TODO: choose your own GOOD implementation - the way here is for code brevity only
    // Or add the logic to some other existing container
    private static IDictionary<Category, CategoryViewModel>  items = new Dictionary<Category, CategoryViewModel>();
    public static CategoryViewModel GetOrCreate(Category cat)
    {
        if (!items.ContainsKey(cat))
            items[cat] = new CategoryViewModel(cat);
        return items[cat];
    }
}

Dann tun Sie dasselbe auf der Seite der Rezept- und problematisch Code festgelegt ist:

  // OLD: Causes infinite loop
  //var catVM = new CategoryViewModel(cat);
  // NEW: Works 
  var catVM = CategoryViewModelFactory.GetOrCreate(cat);

Achtung: mögliche Speicherlecks

Eine Sache, die Sie sollten sich bewusst sein (und das ist auch, warum Sie sollten nicht die Verwendung Dummy-a-la-Fabrik Implementierung) ist die Tatsache, dass diese Schöpfer Objekte halte Referenzen sowohl für die Modellobjekte und ihre Ansicht Wrapper. Daher ist der GC wird sie nicht aus dem Speicher in der Lage zu reinigen.

-1a: die meisten wahrscheinlich haben Sie einen Controller (oder Kontext) in Ihrer Anwendung bereits, auf die Ansichten zugreifen können. In diesem Fall statt diejenigen zu schaffen a-la Fabriken , würde ich nur GetOrCreate Methoden in diesem Kontext bewegen. In diesem Fall, wenn der Kontext gegangen ist (die Form geschlossen ist), auch werden diese Wörterbücher dereferenziert und das Leck Problem ist weg.

Ich würde Sie empfehlen, die gegenseitigen Abhängigkeit loszuwerden, zum Beispiel durch die Abhängigkeit Inversion Prinzip http: //en.wikipedia.org/wiki/Dependency_inversion_principle - zumindest eine der beiden Seiten Foo und Bar (oder deren Wrapper) auf einer abstrakten Schnittstelle abhängen, die die andere Seite implementiert, anstatt zwei konkrete Klassen mit direkt voneinander abhängig sind, die leicht kreis Abhängigkeit und gegenseitige Rekursion Alpträume wie die Sie beobachten produzieren kann. Auch gibt es alternative Möglichkeiten, viele-zu-viele Beziehungen zu implementieren, die eine Überlegung wert sein kann (und kann zur Inversion der Abhängigkeit durch die Einführung geeigneter Schnittstellen zu Thema leichter).

Ich werde sagen Factory-Pattern . Auf diese Weise können Sie die jeweils wiederum konstruieren, fügen sie dann miteinander, dann bringt sie alle versteckt vor neugierigen Blicken durch die Fabrik.

Das erinnert mich an die Art und Weise Serialisierung Endlosschleifen verhindert, wenn Objekte andere Objekte enthalten. Es bildet den Hash-Code eines jeden Objekts auf seine Byte-Array so, wenn ein Objekt einen Verweis auf ein anderes Objekt enthält es: a.) Serialisieren nicht das gleiche Objekt zweimal, und b) nicht serialisiert selbst nicht in eine Endlosschleife

Sie haben im Wesentlichen das gleiche Problem. Die Lösung könnte genauso einfach sein wie eine Art von Karte statt Listensammlung mit. Wenn das, was du bist auf immer eine many-to-many ist dann erstellen Sie einfach eine Karte von Listen.

Optionen:

  1. implementieren eine Mitgliedschaft Test, z.B. überprüfen Bar-is-Mitglied-of-foo vor dem Hinzufügen
  2. bewegen Sie die many-to-many-Beziehung zu seiner eigenen Klasse

letzteres bevorzugt ist, denke ich - es relational Sound

natürlich mit einem foo-bar Beispiel wissen wir wirklich nicht, was das Ziel ist es, so die Leistung kann variieren

EDIT:. Der Code in der ursprünglichen Frage gegeben, # 1 wird nicht funktionieren, weil die unendliche Rekursion geschieht, bevor irgendetwas jemals zu einer Liste hinzugefügt wird

Es gibt mehrere Probleme mit diesem Ansatz / Frage, wahrscheinlich, weil es bis zu dem Punkt in der Nähe von Albernheit abstrahiert worden ist - gut zur Veranschaulichung der Codierung Problem, nicht so gut zur Erläuterung der ursprünglichen Absicht / Ziel:

  1. die Wrapper-Klassen tatsächlich nichts wickeln oder irgendein nützliches Verhalten hinzuzufügen; dies macht es schwierig zu verstehen, warum sie gebraucht werden
  2. mit der angegebenen Struktur, können Sie nicht die Listen im Konstruktor initialisieren an alle , weil jede Wrapper Liste sofort eine neue Instanz der anderen Wrapper Liste erstellt
  3. , auch wenn Sie die Initialisierung von der Konstruktion zu trennen, können Sie immer noch eine zyklische Abhängigkeit mit verborgener Mitgliedschaft (dh verweisen die Wrapper sie aber die foo / bar Elemente verstecken eine Überprüfung enthält, die nicht wirklich wichtig, weil der Code nie bekommt alles zu jeder Liste hinzufügen sowieso!)
  4. Direkt würde relationaler Ansatz arbeitet, erfordert aber die Suche Mechanismen und geht davon aus, dass Wrapper nach Bedarf statt im Voraus erstellt werden würde, z.B. ein Array mit Suchfunktionen oder einem Paar Wörterbücher (zum Beispiel Wörterbuch>, Wörterbuch>) würde für die Zuordnung arbeiten, aber vielleicht nicht Ihr Objektmodell passen

Fazit

Das glaube ich nicht die Struktur wie funktioniert. Nicht mit DI, nicht mit einer Fabrik, nicht -. Weil die Umhüllungen verweisen sie, während die Unterlisten versteckt

Diese Struktur weist auf unausgesprochene falschen Annahmen, aber ohne Zusammenhang können wir nicht aufzustöbern, was sie auch sein mögen.

Bitte neu formuliert das Problem im ursprünglichen Kontext mit realen Objekten und gewünschtem Ziel / Absicht.

Oder zumindest mitteilen, welche Struktur Sie Ihren Beispielcode denken sollte produzieren. ; -)

Nachtrag

Danke für die Aufklärung, das macht die Situation verständlich.

Ich habe nicht mit WPF Datenbindung gearbeitet - aber ich habe entrahmter in diesem MSDN-Artikel - so können folgende sein oder nicht hilfreich und / oder zu korrigieren:

  • Ich denke, die Kategorien und Rezepte Sammlungen in den View-Modellklassen überflüssig sind
    • Sie haben bereits die M: M Informationen in der zugrunde liegenden Kategorie Objekt, also warum duplizieren es in der Ansicht-Modell
    • es sieht aus wie Ihre Sammlung-Handler geändert wird auch eine unendliche Rekursion
    • verursachen
    • die Sammel-Handler geändert werden nicht den zugrunde liegenden M zu aktualisieren: M Informationen für die eingewickelt Rezept / Kategorie
  • Ich denke, der Zweck des Sicht Modell ist die zugrundeliegenden Modelldaten zu belichten, nicht indivudally jeder ihrer Komponenten zu wickeln.
    • Dies scheint überflüssig und eine Verletzung der Kapselung
    • Es ist auch die Quelle Ihrer unendlich Rekursion Problem
    • Naiv, würde ich die ObservableCollection Eigenschaften erwarten, lediglich das zugrunde liegende Modell Sammlungen zurückkehren ...

Die Struktur, die Sie haben, ist eine „invertierte Index“ Darstellung einer many-to-many-Beziehung, die für eine optimierte Abfragen und Abhängigkeitsmanagement durchaus üblich ist. Es reduziert auf ein Paar von einer Eins-zu-viele-Beziehungen. Schauen Sie sich die GamesViewModel Beispiel in dem MSDN-Artikel - beachten Sie, dass die Spiele Eigenschaft ist nur

ObservableCollection<Game>

und nicht

ObservableCollection<GameWrapper>

So, Foo und Bar sind die Models. Foo ist eine Liste von Bars und Bar ist eine Liste von Foos. Wenn lese ich das richtig haben Sie zwei Objekte, die nichts anderes sind als Behälter voneinander. A ist die Menge aller Bs und B ist die Menge aller Wie? Ist das nicht kreisförmig von Natur aus? Es ist eine unendliche Rekursion durch seine Definition. Ist die reale Welt Fall sind mehr Verhalten? Vielleicht ist das, warum Menschen eine schwierige Zeit zu erklären, eine Lösung haben, werden.

Mein einziger Gedanke ist, dass, wenn dies wirklich absichtlich ist dann statische Klassen zu verwenden oder eine statische Variable zu verwenden, um aufzuzeichnen, dass die Klassen einmal erstellt worden war und nur einmal.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top