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