在C#中实施强制一对多关系?

时间:2010-09-12 18:01:47

标签: c# collections containers parent-child relationship

在我的应用程序中,我正在使用Simfile和Notechart对象。 Simfile本质上是一个Notechart容器,具有以下约束:

1)每个记事图必须始终包含在一个父Simfile中 2)给定一个Simfile对象,我需要能够获得所有包含的Notecharts(应该很容易) 3)给出一个Notechart,我需要能够获得它的父Simfile(更难)。

我目前的实现是使用Dictionary作为Simfile基类,因为每个Notechart都有一个唯一的键,至少在Simfile中。我面临的问题是如何强制Notechart总是有一个父Simfile?现在,我可以独立于Simfile创建一个Notechart,并将其添加到Simfile字典中,而无需从Notechart中获取所需的参考 - > Simfile。

理想情况下,最佳分辨率是Simfile中的Add()方法,只能在Notechart的构造函数中调用,但我认为不可能。

我想说这是一个常见问题,但这是我第一次遇到它,而且我不确定它叫什么。

6 个答案:

答案 0 :(得分:2)

实现此目的的一种方法是不允许外部调用者创建NoteChart的实例,并在Simfile上创建一个方法,该方法创建并返回已连接到Simfile实例的NoteChart实例。

这是一个简单的例子,请注意我没有为重复的ID等添加任何错误检查。

public class NoteChart
{
  public SimFile Owner {get; private set;}
  public string Id { get; private set; }

  internal NoteChart(SimFile owner, string id)
  {
    this.Owner = owner;
    this.Id = id;
    Owner.Add(id, this);
  }
}

public class SimFile : Dictionary<string, NoteChart>
{
  public NoteChart CreateNoteChart(string id)
  {
    NoteChart noteChart = new NoteChart(this, id);
    return noteChart;
  }
}

使用它将类似于以下

  // Get instance of SimFile somehow. I just newed it here for the example
  SimFile sim = new SimFile();

  // Create a new NoteChart on the SimFile
  NoteChart chart = sim.CreateNoteChart("NoteChart-1");

这里的事情是NoteChart构造函数标记为内部。这意味着其他程序集中的其他类将无法直接构造NoteChart类的实例,并将被迫通过SimFile.CreateNoteChart方法。

在同一个程序集中,代码仍然可以调用internal构造函数,但必须传递拥有的SiteFile和Id,构造函数会将NoteChart添加到SimFile。

您可能想重新考虑从Dictionary继承SimFile的设计选择,最好只将Dictionary作为成员并公开您需要的功能。例如,现在NoteChart可能会无意中通过SimFle.Add直接添加到另一个SimFile实例,该SimFle.Add继承自Dictionary,并且ID不匹配。但这只是一些思考的问题。

答案 1 :(得分:2)

您可以使用与此类似的代码结构:

public class Simfile
{
   public Simfile()
   {
      Notecharts = new List<Notechart>();
   }
   public List<Notechart> Notecharts { get; private set; }
}

public class Notechart
{
   public Notechart(Simfile simfile)
   {
      Simfile = simfile;
      simfile.Notecharts.Add(this);
   }
   public Simfile Simfile { get; private set; }
}

您需要确保的是,您添加到Notechart类的每个其他构造函数都必须使用Simfile参数。

编辑(根据评论)


如果您不想确保将Notechart添加到Simfile中,如果它不是它的父SIM文件,您可以执行此更改:

public class Simfile
{
   public Simfile()
   {
      ChildNotecharts = new List<Notechart>();
   }
   private List<Notechart> ChildNotecharts { get; set; }
   public IEnumerable<Notechart> Notecharts
   {
      get { return ChildNotecharts; }
   }
   public int AddNotechart(Notechart notechart)
   {
      if (ChildNotecharts.Contains(notechart)) return 0;
      if (notechart.Simfile != this) return -1;
      ChildNotecharts.Add(notechart);
      return 1;
   }
}

public class Notechart
{
   public Notechart(Simfile simfile)
   {
      Simfile = simfile;
      simfile.AddNotechart(this);
   }
   public Simfile Simfile { get; private set; }
}

答案 2 :(得分:1)

以下是两种可能的解决方案。

在NoteChart中有一个接受Simfile对象的构造函数,并调用添加NoteChart的Simfile对象中的函数。如果您有这样的参数化构造函数,则默认构造函数不再可用。像这样:

public class SimFile
{
    public void AddNoteChart(NoteChart nc)
    {
        // Here you can add the note chart to the SimFile dictionary
    }
}

public class NoteChart
{
    public SimFile PapaSimFile { get; private set; }

    NoteChart(SimFile simFile)
    {
        if (simFile == null)
        {
            // throw exception here
        }
        PapaSimFile = simFile;
        PapaSimFile.AddNoteChart(this);
    }
}    

另一种选择是只允许从SimFile中访问NoteChart。例如:

public class SimFile
{
    public class NoteChart
    {
        public SimFile SimFile { get; internal set; }
    }

    public NoteChart CreateNoteChart()
    {
        NoteChart nc = new NoteChart();
        nc.SimFile = this;
        // Here you can add the note chart to the SimFile dictionary
        return nc;
    }
}

每种方法都有优点和缺点,你应该看一下整个画面,以便决定哪种方法更好。

答案 3 :(得分:1)

您的主要要求似乎是您不希望外部代码修改SimFile,除非创建新的NoteCharts。所以我们肯定需要:

  • 限制对NoteChart构造函数的访问(否则您可能会有一个NoteChart实例,其父级SimFile实际上并不包含它<)

  • 限制对SimFile字典的访问(否则其他代码可能会添加内容)

  • 允许访问SimFile字典的只读功能。

我将如何做到这一点。

public class NoteChart
{
    public SimFile Parent { get; private set; }
    public string Id { get; private set; }

    // internal: code outside this assembly cannot instantiate this class
    internal NoteChart(SimFile parent, string id)
    {
        this.Parent = parent;
        this.Id = id;
        Parent.dictionary.Add(id, this);
    }
}

// Implement only IEnumerable, as that is read-only;
// all the other collection interfaces are writable
public class SimFile : IEnumerable<KeyValuePair<string, NoteChart>>
{
    // internal: not accessible to code outside this assembly
    internal Dictionary<string, NoteChart> dictionary =
        new Dictionary<string, NoteChart>();

    // Public method to enable the creation of a new note chart
    // that is automatically associated with this SimFile
    public NoteChart CreateNoteChart(string id)
    {
        NoteChart noteChart = new NoteChart(this, id);
        return noteChart;
    }

    // Read-only methods to retrieve the data. No writable methods allowed.
    public NoteChart this[string index] { get { return dictionary[index]; } }
    public IEnumerator<KeyValuePair<string, NoteChart>> GetEnumerator() {
        return dictionary.GetEnumerator(); }
    public int Count { get { return dictionary.Count; } }
    public IEnumerable<string> Keys { get { return dictionary.Keys; } }
    public IEnumerable<NoteChart> Values { get { return dictionary.Values; } }
}

答案 4 :(得分:0)

以下实现了一个始终具有父SimFile的Notechart。它的工作原理是隐藏容器(以防止在没有父改变的情况下将其添加到SimFile中),并使Notechart成为SimFile类的一部分,以便在更改其父级时可以访问私有集合。这是很自然的,因为你的要求是没有相应的SimFile就不存在记事本;他们真的是SimFiles的一部分。

public class SimFile
{
    private List<Notechart> _notecharts = new List<Notechart>();

    public IEnumerable<Notechart> Notecharts 
    {
        get {return _notecharts.AsEnumerable<Notechart>();}
    }

    public class Notechart
    {
        public Notechart(SimFile parent)
        {
            this.Parent = parent;
        }

        private SimFile _parent = null;
        public SimFile Parent
        {
            get {return _parent;}
            set 
            {
                if (value != null)
                {
                    if (_parent != null)
                    {
                        _parent._notecharts.Remove(this);
                    }
                    _parent = value;
                    _parent._notecharts.Add(this);
                }
                else
                {
                    throw new Exception("A Notechart MUST have a parent SimFile");
                }
            }
        }
    }
}

答案 5 :(得分:0)

好吧,我明白了。我将Notechart类抽象化(完全实现,但是抽象以防止其他类实例化它),然后使用受保护的构造函数在Simfile中创建一个内部类(NotechartWorking)。然后我按照这里的建议让Simfile为我生成笔记图。所以,我正在创建NotechartWorking对象,但是将它们作为Notecharts返回,最终效果很好。