Mvvm 如何避免这种无限循环?

Mvvm 如何避免这种无限循环?,mvvm,wrapper,infinite-loop,stack-overflow,Mvvm,Wrapper,Infinite Loop,Stack Overflow,感觉一定有一些半简单的解决办法,但我就是想不出来 编辑:前面的示例更清楚地显示了无限循环,但这提供了更多的上下文。查看预编辑以快速了解问题 以下两个类表示Model()模式的视图模型 // ///配方的用户界面友好包装器 /// 公共类RecipeViewModel:ViewModelBase { /// ///获取包装好的配方 /// 公共配方RecipeModel{get;private set;} 私有ObservableCollection类别=新ObservableCollect

感觉一定有一些半简单的解决办法,但我就是想不出来

编辑:前面的示例更清楚地显示了无限循环,但这提供了更多的上下文。查看预编辑以快速了解问题

以下两个类表示Model()模式的视图模型

//
///配方的用户界面友好包装器
/// 
公共类RecipeViewModel:ViewModelBase
{
/// 
///获取包装好的配方
/// 
公共配方RecipeModel{get;private set;}
私有ObservableCollection类别=新ObservableCollection();
/// 
///为配方创建新的UI友好包装
/// 
///要包装的食谱
公共RecipeViewModel(配方)
{
this.RecipeModel=配方;
((INotifyCollectionChanged)RecipeModel.Categories).CollectionChanged+=基本RecipeCategoriesCollectionChanged;
foreach(RecipeModel.Categories中的变量cat)
{
var catVM=new CategoryViewModel(cat);//导致无限循环
类别。AddifyNewandNotNull(catVM);
}
}
void BaseRecipeCategoriesCollectionChanged(对象发送方,NotifyCollectionChangedEventArgs e)
{
开关(电动)
{
案例NotifyCollectionChangedAction。添加:
categories.Add(新CategoryViewModel(e.NewItems[0]作为类别));
打破
案例NotifyCollectionChangedAction。删除:
categories.Remove(新CategoryViewModel(e.OldItems[0]作为类别));
打破
违约:
抛出新的NotImplementedException();
}
}
//一些属性和其他不相关的东西
公共只读可观测集合类别
{
获取{返回新的ReadOnlyObservableCollection(categories);}
}
公共无效添加类别(类别视图模型类别)
{
RecipeModel.AddCategory(category.CategoryModel);
}
公共无效删除类别(类别视图模型类别)
{
RecipeModel.RemoveCategory(category.CategoryModel);
}
公共覆盖布尔等于(对象对象对象)
{
var comparedRecipe=对象作为RecipeView模型;
if(comparedRecipe==null)
{返回false;}
return RecipeModel==comparedRecipe.RecipeModel;
}
公共覆盖int GetHashCode()
{
返回RecipeModel.GetHashCode();
}
}

//
///类别的UI友好包装器
/// 
公共类类别ViewModel:ViewModelBase
{
/// 
///获取包装的类别
/// 
公共类别CategoryModel{get;private set;}
私有类别视图模型父级;
私有ObservableCollection配方=新ObservableCollection();
/// 
///为类别创建新的UI友好包装
/// 
/// 
公共类别视图模型(类别)
{
this.CategoryModel=类别;
(category.DirectRecipes作为INotifyCollectionChanged)。CollectionChanged+=baseCategoryDirectRecipesCollectionChanged;
foreach(category.DirectRecipes中的变量项)
{
var recipeVM=new RecipeViewModel(item);//导致无限循环
配方。AddifyNewandNotNull(recipeVM);
}
}
/// 
///将配方添加到此类别
/// 
/// 
公共无效添加配方(RecipeViewModel配方)
{
CategoryModel.AddRecipe(recipe.RecipeModel);
}
/// 
///从该类别中删除配方
/// 
/// 
公共无效清除器配方(RecipeViewModel配方)
{
CategoryModel.Remolecipe(recipe.RecipeModel);
}
/// 
///此类别食谱的只读集合
/// 
公共ReadOnlyObservableCollection配方
{
获取{返回新的ReadOnlyObservableCollection(recipes);}
}
私有void baseCategoryDirectRecipesCollectionChanged(对象发送方,NotifyCollectionChangedEventArgs e)
{
开关(电动)
{
案例NotifyCollectionChangedAction。添加:
var recipeVM=new RecipeViewModel((配方)e.NewItems[0],此项);
配方。AddifyNewandNotNull(recipeVM);
打破
案例NotifyCollectionChangedAction。删除:
recipes.Remove(新RecipeViewModel((Recipe)e.OldItems[0]);
打破
违约:
抛出新的NotImplementedException();
}
}
/// 
///比较此对象是否与参数包装相同的类别
/// 
///要与相等进行比较的对象
///如果它们包装相同的类别,则为True
公共覆盖布尔等于(对象对象对象)
{
var comparedCat=对象作为类别视图模型;
if(comparedCat==null)
{返回false;}
return CategoryModel==comparedCat.CategoryModel;
}
/// 
///获取已包装类别的哈希代码
/// 
///哈希代码
公共覆盖int GetHashCode()
{
返回CategoryModel.GetHashCode();
}
}
除非有要求,否则我不会费心显示模型(配方和类别),但它们基本上负责业务逻辑(例如,将配方添加到类别中也会添加链接的另一端,即,如果类别包含配方,则配方也包含在该类别中),并且基本上决定了事情的进展。ViewModels为WPF数据绑定提供了一个很好的界面。这就是包装器类的原因

由于无限循环在构造函数中,并且它试图创建新对象,所以我不能设置布尔标志来防止这种情况,因为两个对象都没有完成构造

我的想法是拥有一个
字典
字典
,它将延迟加载视图模型,但不会创建一个新的视图模型
/// <summary>
/// A UI-friendly wrapper for a Recipe
/// </summary>
public class RecipeViewModel : ViewModelBase
{
    /// <summary>
    /// Gets the wrapped Recipe
    /// </summary>
    public Recipe RecipeModel { get; private set; }

    private ObservableCollection<CategoryViewModel> categories = new ObservableCollection<CategoryViewModel>();

    /// <summary>
    /// Creates a new UI-friendly wrapper for a Recipe
    /// </summary>
    /// <param name="recipe">The Recipe to be wrapped</param>
    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<CategoryViewModel> Categories 
    {
        get { return new ReadOnlyObservableCollection<CategoryViewModel>(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();
    }
}
/// <summary>
/// A UI-friendly wrapper for a Category
/// </summary>
public class CategoryViewModel : ViewModelBase
{
    /// <summary>
    /// Gets the wrapped Category
    /// </summary>
    public Category CategoryModel { get; private set; }

    private CategoryViewModel parent;
    private ObservableCollection<RecipeViewModel> recipes = new ObservableCollection<RecipeViewModel>();

    /// <summary>
    /// Creates a new UI-friendly wrapper for a Category
    /// </summary>
    /// <param name="category"></param>
    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);
        }
    }

    /// <summary>
    /// Adds a recipe to this category
    /// </summary>
    /// <param name="recipe"></param>
    public void AddRecipe(RecipeViewModel recipe)
    {
        CategoryModel.AddRecipe(recipe.RecipeModel);
    }

    /// <summary>
    /// Removes a recipe from this category
    /// </summary>
    /// <param name="recipe"></param>
    public void RemoveRecipe(RecipeViewModel recipe)
    {
        CategoryModel.RemoveRecipe(recipe.RecipeModel);
    }

    /// <summary>
    /// A read-only collection of this category's recipes
    /// </summary>
    public ReadOnlyObservableCollection<RecipeViewModel> Recipes
    {
        get { return new ReadOnlyObservableCollection<RecipeViewModel>(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();
        }
    }

    /// <summary>
    /// Compares whether this object wraps the same Category as the parameter
    /// </summary>
    /// <param name="obj">The object to compare equality with</param>
    /// <returns>True if they wrap the same Category</returns>
    public override bool Equals(object obj)
    {
        var comparedCat = obj as CategoryViewModel;
        if(comparedCat == null)
        {return false;}
        return CategoryModel == comparedCat.CategoryModel;
    }

    /// <summary>
    /// Gets the hashcode of the wrapped Categry
    /// </summary>
    /// <returns>The hashcode</returns>
    public override int GetHashCode()
    {
        return CategoryModel.GetHashCode();
    }
}
ObservableCollection<Game>
ObservableCollection<GameWrapper>
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);
        }
    }
}
var catVM = new CategoryViewModel(cat); //Causes infinite loop
...
var recipeVM = new RecipeViewModel(item); //Causes infinite loop
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];
    }
}
  // OLD: Causes infinite loop
  //var catVM = new CategoryViewModel(cat);
  // NEW: Works 
  var catVM = CategoryViewModelFactory.GetOrCreate(cat);