Be Careful with MemberWiseClone and Reference Fields and ICloneable

by jmorris 8. September 2009 21:01

A common scenario most programmers have faced is the need to clone a cached object, so that when the object is modified on a per request basis - the cached 'orginal' object is not modified. For example, imagine that you have an XML file on disk and an in-memory object that contains the contents of the file (for lists, etc.). If the cost associated with opening the file (IO) and serialaizing is contents into the in-memory object are great and the cost of actually creating a copy of the in-memory object are small, then cloning the objt per request request, modifying it and then discarding per request is the way to go.

However, one small gotcha exists with Object.MemberWiseClone: while it actually creates a brand new object, it copies the references of non-primitive fields from the original object to the new object! So if you are not expecting changes to those fields, you are in for a big surprise:

 

After cloning you would expect that the original and cloned object would be disticnt objects with distinct object graphs, but that is not the case. Note that the Template object implements ICloneable and that templates.CloneTemplate(...) gets the orginal object from a dictionary and then makes a copy of it by calling Template.Clone() which uses MemberwiseClone() to create a copy of itself and return it to the caller.

Note that the MSDN documentation itself describes this behavior with reference type fields and MemberwiseClone():

"The MemberwiseClone method creates a shallow copy by creating a new object, and then copying the nonstatic fields of the current object to the new object. If a field is a value type, a bit-by-bit copy of the field is performed. If a field is a reference type, the reference is copied but the referred object is not; therefore, the original object and its clone refer to the same object." - MSDN

The solution is not use MemberwiseClone in your ICloneable implementation, unless you specifically want this kind of behavior and I am guessing that most folks want a real copy and not a shallow copy in which the object graph of the member variables points to the original objects roots. For example, replace this:

 

   

        #region ICloneable Members

 

        object ICloneable.Clone()

        {

            return MemberwiseClone();

        }

 

        public Template Clone()

        {

            return ((ICloneable)this).Clone() as Template;

        }

 

        #endregion

 

 

With the something like the following:

 

 

 

Tags:

Jeff Morris

Widget Tag cloud not found.

Unable to cast object of type 'ASP.widgets_tag_cloud_widget_ascx' to type 'App_Code.Controls.WidgetBase'.X

Widget Month List not found.

Unable to cast object of type 'ASP.widgets_month_list_widget_ascx' to type 'App_Code.Controls.WidgetBase'.X

Widget Page List not found.

Unable to cast object of type 'ASP.widgets_page_list_widget_ascx' to type 'App_Code.Controls.WidgetBase'.X

Widget Blogroll not found.

Unable to cast object of type 'ASP.widgets_blogroll_widget_ascx' to type 'App_Code.Controls.WidgetBase'.X

Widget Visitor info not found.

Unable to cast object of type 'ASP.widgets_visitor_info_widget_ascx' to type 'App_Code.Controls.WidgetBase'.X

Widget Visitor info not found.

Unable to cast object of type 'ASP.widgets_visitor_info_widget_ascx' to type 'App_Code.Controls.WidgetBase'.X