问题
From time to time I find myself often writing a data structure of "parents" and "children", where:
- A parent has references to 0 to N distinct children.
- A child has a reference to 0 parents or 1 parent.
- The reference must be mutual. For any given parent, any child that it references must also reference the given parent back. For any given child, the parent that it references must reference the given child back.
- It's impossible to violate the above rules through use of members accessible from outside the two class declarations (non-
private
), aside from use of Reflection.
The mental steps one might take before implementing something like this might start with something like this:
public class Parent
{
private readonly List<Child> _children = new List<Child>();
public readonly ReadOnlyCollection<Child> Children = _children.AsReadOnly();
}
public class Child
{
private Parent _parent;
public Parent Parent
{
get
{
return _parent;
}
set
{
if(value == _parent)
return;
if(_parent != null)
{
_parent._children.Remove(this);
_parent = null;
}
if(value != null)
{
value._children.Add(this);
_parent = value;
}
}
}
}
Of course, this will not compile, since Parent._children
is private
. But, you wouldn't want to make it anything but private, since allowing access outside of Child
or Parent
would make it possible to violate the rules in an implementation or elsewhere.
So, a solution I came up with is to nest Child
in Parent
. Nested classes can access private members of the class its nested within:
public class Parent
{
private readonly List<Child> _children = new List<Child>();
public readonly ReadOnlyCollection<Child> Children = _children.AsReadOnly();
public class Child
{
private Parent _parent;
public Parent Parent
{
get
{
return _parent;
}
set
{
if(value == _parent)
return;
if(_parent != null)
{
_parent._children.Remove(this);
_parent = null;
}
if(value != null)
{
value._children.Add(this);
_parent = value;
}
}
}
}
}
The question I'm asking is, are there any alternative ways to write this that accomplish the same goals which have fewer or less significant drawbacks than this approach? The main drawbacks I'm finding to this approach are:
- This can lead to large scripts, though use of
partial
can help. - This can lead to deeper nesting than desired.
- This can lead to verbose class names. To access
Child
outside ofParent
, you have to useParent.Child
. In cases where the class names are long, and especially when generics are used, this can lead to very ugly code. - Use of generics (for example, to achieve compile-time type safety) can get messy. This appears to at least partially stem from the fact that
Child
is nested andParent<T1>.Child
is a distinct type fromParent<T2>.Child
, and when you want type safety to be mutual this can lead to really ugly use of generics, or needing to fall back to using runtime-enforced type safety (though it can be encapsulated away, usually, e.g., using a non-generic abstract base wherepublic
accessors are insteadprotected
).
On a side note, this may be a good example of where the use of friend
to extend access rights would simplify these problems!
回答1:
I like to use a common, private interface to expose properties like this:
public class SomeBigOuterClass {
private interface IChildFriend {
void SetParent(Parent parent);
}
public class Parent {
}
public class Child : IChildFriend {
void IChildFriend.SetParent(Parent parent) {
this.parent = parent;
}
private Parent parent;
}
}
回答2:
As I stated in the comment, add a Remove method in the parent and that will at least won't expose the whole children list. Something like:
public class Parent
{
private readonly List<Child> _children = new List<Child>();
public readonly ReadOnlyCollection<Child> Children = _children.AsReadOnly();
public void Remove(Child child)
{
if(child !=null)
{
_children.Remove(child);
}
}
}
回答3:
And of course, as stated in comments and in previous answer, you will also need to encapsulate adding childs to parent, which would require public Add(Child)
method
来源:https://stackoverflow.com/questions/42519598/easier-way-to-write-encapsulated-parent-child-data-structure