Note that there are some explanatory texts on larger screens.

plurals
  1. POHow can I avoid this infinite loop?
    primarykey
    data
    text
    <p>It feels like there must be some semi-simple solution to this, but I just can't figure it out.</p> <p>Edit: The previous example showed the infinite loop more clearly, but this gives a bit more context. Check out the pre-edit for a quick overview of the problem.</p> <p>The following 2 classes represent the View-Models of the Model View View-Model (<a href="http://msdn.microsoft.com/en-us/magazine/dd419663.aspx" rel="nofollow noreferrer">MVVM</a>) pattern.</p> <pre><code>/// &lt;summary&gt; /// A UI-friendly wrapper for a Recipe /// &lt;/summary&gt; public class RecipeViewModel : ViewModelBase { /// &lt;summary&gt; /// Gets the wrapped Recipe /// &lt;/summary&gt; public Recipe RecipeModel { get; private set; } private ObservableCollection&lt;CategoryViewModel&gt; categories = new ObservableCollection&lt;CategoryViewModel&gt;(); /// &lt;summary&gt; /// Creates a new UI-friendly wrapper for a Recipe /// &lt;/summary&gt; /// &lt;param name="recipe"&gt;The Recipe to be wrapped&lt;/param&gt; public RecipeViewModel(Recipe recipe) { this.RecipeModel = recipe; ((INotifyCollectionChanged)RecipeModel.Categories).CollectionChanged += BaseRecipeCategoriesCollectionChanged; foreach (var cat in RecipeModel.Categories) { var catVM = new CategoryViewModel(cat); //Causes infinite loop categories.AddIfNewAndNotNull(catVM); } } void BaseRecipeCategoriesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Add: categories.Add(new CategoryViewModel(e.NewItems[0] as Category)); break; case NotifyCollectionChangedAction.Remove: categories.Remove(new CategoryViewModel(e.OldItems[0] as Category)); break; default: throw new NotImplementedException(); } } //Some Properties and other non-related things public ReadOnlyObservableCollection&lt;CategoryViewModel&gt; Categories { get { return new ReadOnlyObservableCollection&lt;CategoryViewModel&gt;(categories); } } public void AddCategory(CategoryViewModel category) { RecipeModel.AddCategory(category.CategoryModel); } public void RemoveCategory(CategoryViewModel category) { RecipeModel.RemoveCategory(category.CategoryModel); } public override bool Equals(object obj) { var comparedRecipe = obj as RecipeViewModel; if (comparedRecipe == null) { return false; } return RecipeModel == comparedRecipe.RecipeModel; } public override int GetHashCode() { return RecipeModel.GetHashCode(); } } </code></pre> <p>.</p> <pre><code>/// &lt;summary&gt; /// A UI-friendly wrapper for a Category /// &lt;/summary&gt; public class CategoryViewModel : ViewModelBase { /// &lt;summary&gt; /// Gets the wrapped Category /// &lt;/summary&gt; public Category CategoryModel { get; private set; } private CategoryViewModel parent; private ObservableCollection&lt;RecipeViewModel&gt; recipes = new ObservableCollection&lt;RecipeViewModel&gt;(); /// &lt;summary&gt; /// Creates a new UI-friendly wrapper for a Category /// &lt;/summary&gt; /// &lt;param name="category"&gt;&lt;/param&gt; public CategoryViewModel(Category category) { this.CategoryModel = category; (category.DirectRecipes as INotifyCollectionChanged).CollectionChanged += baseCategoryDirectRecipesCollectionChanged; foreach (var item in category.DirectRecipes) { var recipeVM = new RecipeViewModel(item); //Causes infinite loop recipes.AddIfNewAndNotNull(recipeVM); } } /// &lt;summary&gt; /// Adds a recipe to this category /// &lt;/summary&gt; /// &lt;param name="recipe"&gt;&lt;/param&gt; public void AddRecipe(RecipeViewModel recipe) { CategoryModel.AddRecipe(recipe.RecipeModel); } /// &lt;summary&gt; /// Removes a recipe from this category /// &lt;/summary&gt; /// &lt;param name="recipe"&gt;&lt;/param&gt; public void RemoveRecipe(RecipeViewModel recipe) { CategoryModel.RemoveRecipe(recipe.RecipeModel); } /// &lt;summary&gt; /// A read-only collection of this category's recipes /// &lt;/summary&gt; public ReadOnlyObservableCollection&lt;RecipeViewModel&gt; Recipes { get { return new ReadOnlyObservableCollection&lt;RecipeViewModel&gt;(recipes); } } private void baseCategoryDirectRecipesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Add: var recipeVM = new RecipeViewModel((Recipe)e.NewItems[0], this); recipes.AddIfNewAndNotNull(recipeVM); break; case NotifyCollectionChangedAction.Remove: recipes.Remove(new RecipeViewModel((Recipe)e.OldItems[0])); break; default: throw new NotImplementedException(); } } /// &lt;summary&gt; /// Compares whether this object wraps the same Category as the parameter /// &lt;/summary&gt; /// &lt;param name="obj"&gt;The object to compare equality with&lt;/param&gt; /// &lt;returns&gt;True if they wrap the same Category&lt;/returns&gt; public override bool Equals(object obj) { var comparedCat = obj as CategoryViewModel; if(comparedCat == null) {return false;} return CategoryModel == comparedCat.CategoryModel; } /// &lt;summary&gt; /// Gets the hashcode of the wrapped Categry /// &lt;/summary&gt; /// &lt;returns&gt;The hashcode&lt;/returns&gt; public override int GetHashCode() { return CategoryModel.GetHashCode(); } } </code></pre> <p>I won't bother showing the Models (Recipe and Category) unless requested, but they basically take care of the business logic (for instance adding a recipe to a category will also add the other end of the link, i.e. if a category contains a recipe, then the recipe is also contained in that category) and basically dictate how things go. The ViewModels provide a nice interface for WPF databinding. That's the reason for the wrapper classes</p> <p>Since the infinite loop is in the constructor and it's trying to create new objects, I can't just set a boolean flag to prevent this because neither object ever gets finished being constructed. </p> <p>What I'm thinking is having (either as a singleton or passed in to the constructor or both) a <code>Dictionary&lt;Recipe, RecipeViewModel&gt;</code> and <code>Dictionary&lt;Category, CategoryViewModel&gt;</code> that will lazy-load the view models, but not create a new one if one already exists, but I haven't gotten around to trying to see if it'll work since it's getting late and I'm kinda tired of dealing with this for the past 6 hours or so.</p> <p>No guarantee the code here will compile since I took a bunch of stuff out that was unrelated to the problem at hand.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload