gpt4 book ai didi

mvvm - 我怎样才能避免这个无限循环?

转载 作者:行者123 更新时间:2023-12-02 06:45:37 25 4
gpt4 key购买 nike

感觉必须有一些半简单的解决方案,但我就是想不通。

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

以下 2 个类表示模型 View View 模型 (MVVM) 模式的 View 模型。

/// <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();
}
}

除非有要求,否则我不会费心展示模型(配方和类别),但它们基本上负责业务逻辑(例如,将配方添加到类别也会添加链接的另一端,即如果类别包含一个食谱,然后该食谱也包含在该类别中)并且基本上决定了事情的进展。 ViewModels 为 WPF 数据绑定(bind)提供了一个很好的接口(interface)。这就是包装类的原因

由于无限循环在构造函数中并且它正在尝试创建新对象,我不能只设置一个 bool 标志来防止这种情况发生,因为两个对象都没有完成构造。

我在想的是(作为单例或传递给构造函数或两者)一个 Dictionary<Recipe, RecipeViewModel>Dictionary<Category, CategoryViewModel>这将延迟加载 View 模型,但如果已经存在则不会创建新 View 模型,但我还没有抽出时间尝试看看它是否会起作用,因为它已经很晚了,我有点厌倦了处理这个过去 6 个小时左右。

不能保证这里的代码会编译,因为我去掉了一堆与手头问题无关的东西。

最佳答案

回到您最初的问题(和代码)。如果您想要的是自动同步的多对多关系,请继续阅读。寻找处理这些情况的复杂代码的最佳位置是任何 ORM 框架的源代码,这对于这个工具领域来说是非常常见的问题。我会查看 nHibernate (https://nhibernate.svn.sourceforge.net/svnroot/nhibernate/trunk/nhibernate/) 的源代码,了解它如何实现处理 1-N 和 M-N 关系的集合。

您可以尝试的简单方法是创建您自己的小集合类来处理它。下面我删除了您原来的包装类并添加了一个 BiList 集合,该集合使用对象(集合的所有者)和属性另一端的名称进行初始化以保持同步(仅适用于 M-N,但 1- N 很容易添加)。当然,你会想要润色代码:

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);
}
}
}

关于mvvm - 我怎样才能避免这个无限循环?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/904999/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com