문제

그것은 같은 느낌이 있어야 합 일부 세미에 간단한 솔루션이지만,저는 그것을 알아낼 수 없습니다.

편집:이전의 예를 보여 주었다 무한 루프를 더 명확하게 하지만,조금 더다.체크아웃 전 편집한 개요 문제입니다.

다음과 같은 2 개의 클래스를 나타내는 뷰 모델 모델의 보기 보 모형(된 이)패턴이다.

/// <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 데이터 바인딩.그 이유는 래퍼 클래스

이후 무한 루프에서 생성자와 그것을 만들려고 새로운 개체로,나는 할 수 없으로 설정할 수 있습니다 boolean flag 이를 방지하기 때문에도 객체가 이제까지 완료된 구성됩니다.

내가 무슨 생각을 하는 데(으로 단일 또는 전달에서 생성자 또는 모두)에 Dictionary<Recipe, RecipeViewModel>Dictionary<Category, CategoryViewModel> 는 것으로드 모델을 볼 수 있지만,새로 만들지 중 하나 이미 존재하는 경우,그러나 내가 주위를 확보하지 못했는지 확인하기 위해 노력하고 그것을 작동 이후 늦었고 지쳤을 다루고 이에 대해 지난 6 시간 정도입니다.

없음을 보증 코드는 여기에서 컴파일하기 때문했 물건의 무리는 비 문제를 손에 있습니다.

도움이 되었습니까?

해결책

사람,내가 응답하지 않으로 멋진으로 DI 것들입니다.하지만...

간단히 말해,저는 생각을 만들어야 합니다 래퍼를 시작하기 전에 관련된다.트래버스 전체 목록 Foos 을 만들고,FooWrappers.그 후 통과 바를 만들 BarWrappers.다음 읽기 소스 Foos,추가 적절한 BarWrapper 참조하 MyBarWrappers 에 관련된 FooWrapper,그 반대의한 바가 있습니다.

당신이 주장하는 경우에는 모두를 만드는 래퍼 Foo 인스턴스와 즉시 관계를 만드는 각각의 바 경우,당신은 휴식""주기를 표시하여 푸에서 작업하는,즉Foo_1,그리고 각각의 BarWrapper 인스턴스 알고 있을 만들지 않는 또 다른 FooWrapper_1 인스턴스 내부의 MyFooWrappers 컬렉션입니다.결국,당신은 사실 이미 만들기 FooWrapper_1 높은업(또는 추가로,그것이 있었다)호출 스택입니다.

Bottom line:의 문제로는 코드 정신 래퍼가 생성자해야하지 않을 만들 수 있는 더 중요한 헌납자이었습니다.에서 매우 가장-그것은 단지 알고 있/찾을 수 있는 하나의 고유한 래퍼가 존재에 대한 다른 곳에서 각 Foo 과 바,그리고 어쩌면 만드는 래퍼지 않는 경우에만 그것을 찾을 수 있습니다.

다른 팁

원래 질문 (및 코드)으로 돌아갑니다. 원하는 것이 자동으로 동기화되는 많은 2 마리의 관계를 갖는 것이라면 계속 읽으십시오. 이러한 사례를 처리하는 복잡한 코드를 찾을 수있는 가장 좋은 곳은 ORM 프레임 워크의 소스 코드 이며이 도구 영역에서는 매우 일반적인 문제입니다. 나는 nhibernate의 소스 코드를 살펴볼 것이다 (https://nhibernate.svn.sourceforge.net/svnroot/nhibernate/trunk/nhibernate/) 1-N 관계를 모두 처리하는 컬렉션을 구현하는 방법을 확인합니다.

당신이 시도 할 수있는 간단한 것은 그냥 돌보는 자신만의 작은 컬렉션 클래스를 만드는 것입니다. 아래는 원래 래퍼 클래스를 제거하고 Bilist 컬렉션을 추가했습니다. Bilist 컬렉션은 객체 (컬렉션의 소유자)와 속성의 다른 쪽 이름으로 초기화되어 있습니다 (MN의 경우에만 작동하지만 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);
        }
    }
}

가장 먼저 DI 문제를 해결하지는 않지만 항상 관련된 것은 DI 문제를 해결할 것입니다. 컨테이너 (또는 조회 기능이있는 컨텍스트)

해결책:

이 장소에서 코드가 실패합니다.

var catVM = new CategoryViewModel(cat); //Causes infinite loop
...
var recipeVM = new RecipeViewModel(item); //Causes infinite loop

문제는 이미 존재하더라도 객체에 대한 래퍼 (xxxviewmodel)를 생성한다는 사실로 인해 발생합니다. 동일한 객체에 대한 래퍼를 다시 작성하는 대신이 모델의 래퍼가 이미 존재하는지 확인하고 대신 사용해야합니다. 따라서 생성 된 모든 개체를 추적하려면 컨테이너가 필요합니다. 귀하의 옵션은 다음과 같습니다.

옵션 1: 간단한 A-LA 공장 패턴을 사용하여 객체를 만들뿐만 아니라 다음을 추적하십시오.

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

조심 : 메모리 누출 가능?

당신이 알아야 할 한 가지는 더미 A-LA 공장 구현)은 이것입니다 창조자 객체는 모델 객체와 뷰 포장지에 대한 참조를 유지합니다. 따라서 GC는 메모리에서 청소할 수 없습니다.

옵션 -1A : 대부분 응용 프로그램에 컨트롤러 (또는 컨텍스트)가있을 것입니다. 이 경우를 만드는 대신 A-LA 공장, 나는 단지 getorcreate 방법을이 맥락으로 옮길 것입니다. 이 경우 컨텍스트가 사라지는 경우 (양식이 닫히면) 사전도 참조되지 않고 누출 문제가 사라집니다.

예를 들어 의존성 반전 원칙을 통해 상호 의존성을 제거하는 것이 좋습니다. http://en.wikipedia.org/wiki/dependency_inversion_principle -양면 중 하나 이상 Foo 및 Bar (또는 래퍼)가 두 개의 콘크리트 클래스를 직접 의존하는 대신 상대방이 구현하는 추상 인터페이스에 의존하여 원형 의존성과 상호를 쉽게 생성 할 수 있습니다. -당신이 관찰하는 것과 같은 악몽. 또한, 고려할 가치가있는 다양한 관계를 구현하는 대안적인 방법이 있습니다 (적합한 인터페이스의 도입을 통해 의존성의 역전을 거부하는 것이 더 쉬울 수 있음).

나는 말할 것이다 공장 패턴. 이렇게하면 각각 차례로 건설 한 다음 서로를 추가 한 다음 공장에서 눈을 사로 잡는 것에 숨겨진 모든 것을 반환 할 수 있습니다.

이것은 객체에 다른 객체가 포함될 때 직렬화가 무한 루프를 방지하는 방식을 상기시킵니다. 각 객체의 해시 코드를 바이트 배열에 매핑하므로 객체에 다른 객체에 대한 참조가 포함되어있을 때 다음과 같이합니다.

당신은 본질적으로 같은 문제를 가지고 있습니다. 솔루션은 목록 수집 대신 일종의 맵을 사용하는 것만 큼 간단 할 수 있습니다. 당신이받는 것이 다수라면 목록지도 만 만듭니다.

옵션 :

  1. 추가하기 전에 회원 테스트를 구현합니다.
  2. 다수의 관계를 자신의 수업으로 옮기십시오

후자는 선호됩니다.

물론, foo-bar 예제와 함께 우리는 목표가 무엇인지 알지 못하므로 마일리지가 다를 수 있습니다.

편집 : 원래 질문의 코드가 주어지면 #1은 어떤 목록에 어떤 것이 추가되기 전에 무한 재귀가 발생하기 때문에 #1은 작동하지 않습니다.

이 접근법/질문에는 몇 가지 문제가있을 것입니다. 아마도 근거리가 가까운 시점까지 추상화 되었기 때문에 코딩 문제를 설명하는 데 좋습니다. 원래 의도/목표를 설명하기에 좋지 않습니다.

  1. 래퍼 클래스는 실제로 아무것도 포장하거나 유용한 동작을 추가하지 않습니다. 이것은 왜 필요한지 알기가 어렵습니다
  2. 주어진 구조를 사용하면 생성자의 목록을 초기화 할 수 없습니다. 조금도 각 래퍼 목록은 즉시 다른 래퍼 목록의 새 인스턴스를 만듭니다.
  3. 초기화를 건축과 분리하더라도 여전히 숨겨진 멤버십으로 주기적 종속성이 있습니다 (예 : 랩퍼는 서로를 참조하지만 포함 된 확인에서 foo/bar 요소를 숨기고 코드가 아무것도 추가하지 않기 때문에 실제로 중요하지 않습니다. 어쨌든 모든 목록에!)
  4. 직접적인 관계형 접근 방식은 효과가 있지만 검색 메커니즘이 필요하며 래퍼가 미리 필요하지 않은대로 생성 될 것이라고 가정합니다. 예를 들어 검색 기능이있는 배열 또는 한 쌍의 사전 (예 : 사전>, 사전>)이 매핑에 작동합니다. 그러나 객체 모델에 맞지 않을 수 있습니다

결론

나는 구조를 생각하지 않습니다 설명한대로 작동합니다. 래퍼가 하위 목록을 숨길 때 서로를 참조하기 때문에 공장이 아닌 DI를 사용하지 않습니다.

이 구조는 언급되지 않은 잘못된 가정을 암시하지만 컨텍스트가 없으면 우리는 그들이 무엇을 할 수 있을지를 파악할 수 없습니다.

실제 객체와 원하는 목표/의도를 가진 원래 상황에서 문제를 다시 작성하십시오.

또는 적어도 샘플 코드를 생각하는 구조를 명시하십시오. ~해야 한다 생산하다. ;-)

부록

설명해 주셔서 감사합니다. 이것은 상황을 더욱 이해하기 쉽게 만듭니다.

나는 WPF Databinding과 함께 일하지 않았지만 삐걱 거렸다. 이 MSDN 기사 - 따라서 다음은 도움이되거나 도움이 될 수 있습니다.

  • 뷰 모델 클래스의 카테고리 및 레시피 컬렉션이 중복되었다고 생각합니다.
    • 기본 범주 객체에 이미 m : m 정보가 있으므로 뷰 모델에 복제하는 이유는 무엇입니까?
    • 컬렉션 변경 처리기도 무한 재귀를 유발하는 것 같습니다.
    • 컬렉션 변경 처리기는 랩핑 레시피/카테고리에 대한 기본 M : M 정보를 업데이트하지 않는 것으로 보입니다.
  • 뷰 모델의 목적은 각 구성 요소를 개별적으로 랩핑하지 않고 기본 모델 데이터를 노출시키는 것이라고 생각합니다.
    • 이것은 중복성과 캡슐화 위반으로 보입니다
    • 그것은 또한 당신의 무한한 수석 문제의 원천이기도합니다
    • 순진하게, 나는 관측형 수집 특성이 단지 기본 모델의 컬렉션을 반환 할 것으로 기대합니다 ...

당신이 가진 구조는 다량의 관계의 "반전 인덱스"표현으로, 최적화 된 조회 및 종속성 관리에 매우 일반적입니다. 한 쌍의 일대일 관계로 줄어 듭니다. MSDN 기사의 GamesViewModel 예를보십시오 - 게임 속성은 단지입니다.

ObservableCollection<Game>

그리고 아닙니다

ObservableCollection<GameWrapper>

따라서 Foo와 Bar는 모델입니다. Foo는 막대 목록이며 바는 푸스 목록입니다. 내가 올바르게 읽는다면 서로 컨테이너에 지나지 않는 두 개의 물체가 있습니다. A는 모든 BS 세트와 B가 모두 세트입니까? 그 자연에 의해 원형이 아닌가? 그것은 그 정의에 의한 무한한 재귀입니다. 실제 사례에는 더 많은 행동이 포함되어 있습니까? 아마도 사람들이 해결책을 설명하는 데 어려움을 겪고있는 이유 일 것입니다.

저의 유일한 생각은 이것이 진정으로 의도적 인 경우 정적 클래스를 사용하거나 정적 변수를 사용하여 클래스가 한 번만 생성되었음을 기록하는 것입니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top