Reputation: 44256
Let's take the following two classes:
public class CollectionOfChildren
{
public Child this[int index] { get; }
public void Add(Child c);
}
public class Child
{
public CollectionOfChildren Parent { get; }
}
Child's Parent property should always return the CollectionOfChildren that the Child is in, or null if the child is not in such a collection. Between these two classes, this invariant should be maintained, and should not be breakable (well, easily) by the consumer of the class.
How do you implement such a relationship? CollectionOfChildren cannot set any of the private members of Child, so how is it supposed to inform Child that it was added to the collection? (Throwing an exception is acceptable if the child is already part of a collection.)
The internal
keyword has been mentioned. I am writing a WinForms app at the moment, so everything is in the same assembly, and this is essentially no different than public
.
Upvotes: 9
Views: 8548
Reputation: 1190
I was also looking into it recently, and thought about really enforcing this relationship as error proof as possible. Additionally, I tried to keep it as general and type safe as possible. It may be just overengineered, but still I would like to share it.
public class ChildCollection<TChild> : IEnumerable<TChild>
where TChild : ChildCollection<TChild>.Child
{
private readonly List<TChild> childCollection = new List<TChild>();
private void Add(TChild child) => this.childCollection.Add(child);
public IEnumerator<TChild> GetEnumerator() => this.childCollection.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public abstract class Child
{
private readonly ChildCollection<TChild> childCollection;
protected Child(ChildCollection<TChild> childCollection)
{
this.childCollection = childCollection;
childCollection.Add((TChild)this);
}
}
}
Here comes the Example:
public class Parent
{
public ChildCollection<Child> ChildCollection { get; }
public Parent()
{
ChildCollection = new ChildCollection<Child>();
}
}
public class Child : ChildCollection<Child>.Child
{
public Child(ChildCollection<Child> childCollection) : base(childCollection)
{
}
}
And adding a child to the parent would look like:
var parent = new Parent();
var child1 = new Child(parent.ChildCollection);
The final implementation also has Ids for the children and allows removing of children. But the latter destroys the strong enforcement of the parent child relationship.
Upvotes: 0
Reputation: 59645
My answer contains to solutions - the first one uses nested classes to allow the inner class to access the outer class. Later I realized that there is no need to access private data of the other class, hence no need for nested classe, if the property getters and setters are designed carefully to avoid infinite indirect recursions.
To avoid the problem with internal
fields you can just nest the collection class into the item class and make the field private
. The following code is not exactly what you requestes, but shows how to create a one-to-many relationship and keep it consistent. An Item
may have one parent and many children. If and only if an item has a parent, then it will be in the child collection of the parent. I wrote the code adhoc without testing, but I think there is no way to break this from outsight of the Item
class.
public class Item
{
public Item() { }
public Item(Item parent)
{
// Use Parent property instead of parent field.
this.Parent = parent;
}
public ItemCollection Children
{
get { return this.children; }
}
private readonly ItemCollection children = new ItemCollection(this);
public Item Parent
{
get { return this.parent; }
set
{
if (this.parent != null)
{
this.parent.Children.Remove(this);
}
if (value != null)
{
value.Children.Add(this);
}
}
}
private Item parent = null;
The ItemCollection
class is nested inside the Item
class to gain access to the private field parent
.
public class ItemCollection
{
public ItemCollection(Item parent)
{
this.parent = parent;
}
private readonly Item parent = null;
private readonly List<Item> items = new List<Item>();
public Item this[Int32 index]
{
get { return this.items[index]; }
}
public void Add(Item item)
{
if (!this.items.Contains(item))
{
this.items.Add(item);
item.parent = this.parent;
}
}
public void Remove(Item item)
{
if (this.items.Contains(item))
{
this.items.Remove(item);
item.parent = null;
}
}
}
}
UPDATE
I checked the code now (but only roughly) and I believe it will work without nesting the classes, but I am not yet absolutly sure. It's all about using the Item.Parent
property without causing an infinite loop, but the checks that were already in there and the one I added for efficency protect from this situation - at least I believe it.
public class Item
{
// Constructor for an item without a parent.
public Item() { }
// Constructor for an item with a parent.
public Item(Item parent)
{
// Use Parent property instead of parent field.
this.Parent = parent;
}
public ItemCollection Children
{
get { return this.children; }
}
private readonly ItemCollection children = new ItemCollection(this);
The important part is the Parent
property that will trigger the update of the parent's child collection and prevent from entering a infinte loop.
public Item Parent
{
get { return this.parent; }
set
{
if (this.parent != value)
{
// Update the parent field before modifing the child
// collections to fail the test this.parent != value
// when the child collection accesses this property.
// Keep a copy of the old parent for removing this
// item from its child collection.
Item oldParent = this.parent;
this.parent = value;
if (oldParent != null)
{
oldParent.Children.Remove(this);
}
if (value != null)
{
value.Children.Add(this);
}
}
}
}
private Item parent = null;
}
The the important parts of the ItemCollection
class are the private parent
field that makes the item collection aware of its owner and the Add()
and Remove()
methods that trigger updates of the Parent
property of the added or removed item.
public class ItemCollection
{
public ItemCollection(Item parent)
{
this.parent = parent;
}
private readonly Item parent = null;
private readonly List<Item> items = new List<Item>();
public Item this[Int32 index]
{
get { return this.items[index]; }
}
public void Add(Item item)
{
if (!this.items.Contains(item))
{
this.items.Add(item);
item.Parent = this.parent;
}
}
public void Remove(Item item)
{
if (this.items.Contains(item))
{
this.items.Remove(item);
item.Parent = null;
}
}
}
Upvotes: 2
Reputation: 11518
Could this sequence work for you?
CollectionOfChild.Add(Child c)
CollectionOfChild.Add
invokes Child.UpdateParent(this)
Child.UpdateParent(CollectionOfChild newParent)
calls newParent.Contains(this)
to make sure the child is in that collection then change the backing of Child.Parent
accordingly. Also have to call CollectionOfChild.Remove(this)
to remove itself from the old parent's collection.CollectionOfChild.Remove(Child)
would check Child.Parent
to make sure it's not the child's collection anymore before it would remove the child from the collection.Putting some code down:
public class CollectionOfChild
{
public void Add(Child c)
{
this._Collection.Add(c);
try
{
c.UpdateParent(this);
}
catch
{
// Failed to update parent
this._Collection.Remove(c);
}
}
public void Remove(Child c)
{
this._Collection.Remove(c);
c.RemoveParent(this);
}
}
public class Child
{
public void UpdateParent(CollectionOfChild col)
{
if (col.Contains(this))
{
this._Parent = col;
}
else
{
throw new Exception("Only collection can invoke this");
}
}
public void RemoveParent(CollectionOfChild col)
{
if (this.Parent != col)
{
throw new Exception("Removing parent that isn't the parent");
}
this._Parent = null;
}
}
Not sure if that works but the idea should. It effectively creates and internal method by using Contains as the child's way to check the "authenticity" of the parent.
Keep in mind you can blow all this away with reflection so you really only need to make it slightly hard to get around to deter people. Thomas' use of explicit interfaces is another way to deter, though I think this is a bit harder.
Upvotes: 0
Reputation: 292405
I recently implemented a solution similar to AgileJon's, in the form of a generic collection and an interface to be implemented by child items :
ChildItemCollection<P,T> :
/// <summary>
/// Collection of child items. This collection automatically set the
/// Parent property of the child items when they are added or removed
/// </summary>
/// <typeparam name="P">Type of the parent object</typeparam>
/// <typeparam name="T">Type of the child items</typeparam>
public class ChildItemCollection<P, T> : IList<T>
where P : class
where T : IChildItem<P>
{
private P _parent;
private IList<T> _collection;
public ChildItemCollection(P parent)
{
this._parent = parent;
this._collection = new List<T>();
}
public ChildItemCollection(P parent, IList<T> collection)
{
this._parent = parent;
this._collection = collection;
}
#region IList<T> Members
public int IndexOf(T item)
{
return _collection.IndexOf(item);
}
public void Insert(int index, T item)
{
if (item != null)
item.Parent = _parent;
_collection.Insert(index, item);
}
public void RemoveAt(int index)
{
T oldItem = _collection[index];
_collection.RemoveAt(index);
if (oldItem != null)
oldItem.Parent = null;
}
public T this[int index]
{
get
{
return _collection[index];
}
set
{
T oldItem = _collection[index];
if (value != null)
value.Parent = _parent;
_collection[index] = value;
if (oldItem != null)
oldItem.Parent = null;
}
}
#endregion
#region ICollection<T> Members
public void Add(T item)
{
if (item != null)
item.Parent = _parent;
_collection.Add(item);
}
public void Clear()
{
foreach (T item in _collection)
{
if (item != null)
item.Parent = null;
}
_collection.Clear();
}
public bool Contains(T item)
{
return _collection.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
_collection.CopyTo(array, arrayIndex);
}
public int Count
{
get { return _collection.Count; }
}
public bool IsReadOnly
{
get { return _collection.IsReadOnly; }
}
public bool Remove(T item)
{
bool b = _collection.Remove(item);
if (item != null)
item.Parent = null;
return b;
}
#endregion
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
return _collection.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return (_collection as System.Collections.IEnumerable).GetEnumerator();
}
#endregion
}
IChildItem<T> :
public interface IChildItem<P> where P : class
{
P Parent { get; set; }
}
The only drawback of using an interface is that it's not possible to put an internal
modifier on the set accessor... but anyway, in a typical implementation, this member would be "hidden" behind an explicit implementation :
public class Employee : IChildItem<Company>
{
[XmlIgnore]
public Company Company { get; private set; }
#region IChildItem<Company> explicit implementation
Company IChildItem<Company>.Parent
{
get
{
return this.Company;
}
set
{
this.Company = value;
}
}
#endregion
}
public class Company
{
public Company()
{
this.Employees = new ChildItemCollection<Company, Employee>(this);
}
public ChildItemCollection<Company, Employee> Employees { get; private set; }
}
This is particularly useful when you want to serialize this kind of object in XML : you can't serialize the Parent property because it would cause cyclic references, but you want to keep the parent/child relation.
Upvotes: 1
Reputation: 53586
public class CollectionOfChildren
{
public Child this[int index] { get; }
public void Add(Child c) {
c.Parent = this;
innerCollection.Add(c);
}
}
public class Child
{
public CollectionOfChildren Parent { get; internal set; }
}
Upvotes: 7