title: How can I avoid this infinite loop?
body: <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>