Tuesday, May 10, 2011

When OO Meets Uh-Oh

This will be an ongoing series on pitfalls and gotchas when developing object-oriented components with the .NET framework.

Working With Nested Objects

When creating business entity classes that represent our enterprise data, we typically encounter a need for nested objects. A good example of this is in the categorization of information, such as Product Category:

  • Tools
    • Hand Tools
      • Hammers
      • Screwdrivers
    • Power Tools
      • Drills
      • Sprayers

At first, we may create a simple class to store such data:

    public class NestedObject
{
public NestedObject()
{
this.Id = 0;
this.Name = string.Empty;
this.ModifiedDate = DateTime.MinValue;
this.ParentObject = new NestedObject();
}

public int Id { get; set; }

public string Name { get; set; }

public DateTime ModifiedDate { get; set; }

public NestedObject ParentObject { get; set; }

}



This class stores the information we need, it compiles without error, and we go on about our business, loading the data from a database or XML feed.

    NestedObject tools = new NestedObject()
{
Id = 1,
Name = "Tools"
};

NestedObject powerTools = new NestedObject()
{
Id = 2,
Name = "Power Tools",
ParentObject = tools
};



However, the problem is exposed when we execute this code, courtesy of the StackOverflowException. Notice in our constructor that we are initializing our Parent Object property. We have inadvertently caused infinite recursion, because as each Nested Object is created, it attempts to create another as its parent, which then also attempts to create its parent, ad infinitum.


Simple Solution


One solution would be to instantiate the Parent Object property as needed, or when the property getter is accessed:

    public class NestedObject
{
private NestedObject _parentObject;

public NestedObject()
{
this.Id = 0;
this.Name = string.Empty;
this.ModifiedDate = DateTime.MinValue;
}

public int Id { get; set; }

public string Name { get; set; }

public DateTime ModifiedDate { get; set; }

public NestedObject ParentObject
{
get
{
if (this._parentObject == null)
this._parentObject = new NestedObject();
return this._parentObject;
}
set { this._parentObject = value; }
}

}



Notice how we defer the object instantiation from the constructor to the property getter. Now, our code still compiles, and when executed, works as expected – unless this object will be XML serialized by a Web or WCF service.


Nested Objects and XML Serialization


When an object, or list of objects, is returned by a method in a Web or WCF service using SOAP, each object is XML serialized for transmission (and then deserialized by the client). Using reflection, the value of each property in the object will be retrieved during serialization, and set when the object is recreated by the client. If a property is another class, composed of members like our Nested Object class, the sub-properties of that class will be discovered, and so forth. 


Let’s create a web service that returns a list of Nested Objects:

        [WebMethod]
public List<NestedObject> GetNestedObjects()
{
List<NestedObject> list = new List<NestedObject>();
NestedObject tools = new NestedObject()
{
Id = 1,
Name = "Tools"
};
list.Add(tools);

NestedObject powerTools = new NestedObject()
{
Id = 2,
Name = "Power Tools",
ParentObject = tools
};
list.Add(powerTools);

return list;
}



When this web method is invoked, we get the same StackOverflowException. The code itself executes without issue, but upon XML serialization, as the properties are discovered, we encounter the infinite recursion issue.


What is the Best Solution?


To answer the question of how best to handle this situation: It depends. Your solution should take this recursion situation into consideration, but the final implementation depends on how you manage your data, code, validation, and everything else. If the data will be displayed in a tree view or similar control, your business entity classes can take the top-down approach (where the children are in a property of the parent). In most cases, however, there is no reason for the entire hierarchy to be constructed and populated in your objects at run-time (but again, this depends on your exact needs).

No comments:

Post a Comment