views:

692

answers:

4

Hi, this must be such a common scenario that there's been a lot written about it already, hopefully even a really good pattern. I have a domain model in which a custom container contains entities. For example (properties and interfaces excluded for brevity):

class Entity
{
    public int Id;
    public EntityContainer ParentContainer;
}


class EntityContainer
{
    public int Id;
    public IList<Entity> Entities = new List<Entity>();

    public void AddEntity(Entity entity)
    {
        entity.ParentContainer = this;
        Entities.Add(entity);
    }
}


class Main
{
    public Main()
    {
        Entity entity1 = new Entity();
        Entity entity2 = new Entity();
        EntityContainer entityContainer = new EntityContainer();
        entityContainer.AddEntity(entity1);
        entityContainer.AddEntity(entity2);

        // Can now traverse graph easily, e.g.
        Console.WriteLine("entity1's parent container ID = " + entity1.ParentContainer.Id);
        Console.WriteLine("Container contains at least this entity ID: " + entityContainer.Entities[0].Id);

    }
}

I can now easily traverse my object graph both ways, but have created a circular reference. Would you create a third type to divorce the dependencies?

Thanks in advance

+2  A: 

Does the container need to know about the type of the contents? If not, generics can avoid this - i.e. Container<T>, where you happen to use Container<Entity>. Other than that; pushing the necessary details into an interface (or base-class) in an assembly that both can reference is a common approach.

Personally, I'd try to simply avoid the need for the child to know about the parent.

Also; note that if you do go down the abstraction (interface etc) route; this can have a big implication if you are using (for example) xml serialization.


(edit re comments) OK; first: what problem is the circular reference (within an assembly) causing; if none, leave it alone. If there is a problem, then you'll need an extra type; presumably some interfaces to represent the concrete types - i.e. where Entity : IEntity, and EntityContainer only knows about IEntity (or vv with IEntityContainer, or both),

Marc Gravell
The child needs to know about its parent for performance reasons.
ng5000
Generica wouldn't be suitable for the actual problem domain I'm modelling. These are specific types and relationships.
ng5000
Thanks for the update - I will be adding interfaces. I think the general consensus of there's actually nothing wrong with circular references is the main take-away from this question.
ng5000
+3  A: 

There's nothing wrong with circular references, per se, and they are used extensively in the .NET Framework, e.g. XmlNode.OwnerDocument, Control.Parent.

If you need to traverse up the tree, then a back reference is fine to use.

In COM, circular references are tricky because if you were the set the container and all its children to nothing, then objects will not be cleaned up up properly as the children still hold references to the parent. However the .NET garbage collection has no problem with this the way it is implemented.

Patrick McDonald
+1  A: 

As so I don't see a problem with your class model but you could easily make a clean cut. Make Entity implement an interface IEntity and make EntityContainer hold a IList and unless you have a very specific reason for using IList you should considere IEnumerable instead. Calling .ToList() on a List is practically free and it would make it easy for the consumer of the EntityClass you use. Since passing any array of IEntity, or linq expression selecting IEntities would be possible

Rune FS
Calling Enumerable.ToList() on a List is most definitely not free, as it always produces a new list - so it'll have to make a copy, which is O(N).
Pavel Minaev
A: 

Using circular referenced objects is ok in my book providing you are using some kind of lazy-loading pattern when designing those objects.

e.g.

You want to access: Company.Employee And in another scenario: Employee.Company

Which makes a circular reference i.e. Company.Employee.Company.Employee etc.

If these properties are not lazy-loaded e.g. A Company object always loads its Employee property and the Employee object always loads its Company property then it aint gonna work too well when you introduce a data source.

iandayman