Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
Session, ForEach, and a ShallowCopy walk into a bar...

imageBefore we get started, let's play "questions you only hear during an interview."   Are the contestants ready?  Good, here is the question:  What is the difference between a shallow copy and a deep copy? 

MSDN on Object.MemberwiseClone: "Consider an object called X that references objects A and B. Object B, in turn, references object C. A shallow copy of X creates new object X2 that also references objects A and B. In contrast, a deep copy of X creates a new object X2 that references the new objects A2 and B2, which are copies of A and B. B2, in turn, references the new object C2, which is a copy C."

Clear as mud?  Okay, think of it this was - if you made a shallow copy of a directory it would copy only the files in the top level of the directory and create shortcut links to the subfolders.  A deep copy would copy the files, all subfolders and their file, and the subfolder's subfolders and files, etc.  In C#/.Net world, 99% of the time you are making shallow copies.

It common for me to have an asp.net app that gets data from a webservice, and then applies filters selected by the user.  Since the user will change sorting, filters, etc with the same set of data several times, I cache the call to the webservice in Session.  (Note: It's okay because I expect only 2-4 user's at a time on these, mostly internal applications - don't do this if you need to scale beyond a small set of users!).  It looks something like:

    public ShowProduct[] Products { 
get {
return Session["Products"] == null ?
new ShowProduct[0] :
(ShowProduct[])Session["Products"];
}
set {
Session["Products"] = value;
}
}

protected void LoadProducts()
{
ShowServiceSoap svc = new ShowServiceSoap();
Products = svc.GetProductsByShowID(
String.Empty,
Convert.ToDecimal(ddlShows.SelectedValue));
}

protected void FilterProducts() {
ShowProduct[] products = Products;

if (cbFilterSizeable.Checked) {
products = (from p in products
where p.Sizeable.Equals(true)
select p).ToArray();
}

gvProducts.DataSource = products;
gvProducts.DataBind();
}

This works just fine.  In practice, there are many steps in the FilterProducts method and I add some dependency injection options beyond using a property to access session, but you get the idea.  These methods are called from event handlers on the web form. 

Now, I had a request to add an option to filter product videos to only .AVI, which are stored in the ShowProduct.Video list, so I changed FilterProducts like so:

    protected void FilterProducts() {
ShowProduct[] products = Products;

if (cbFilterSizeable.Checked) {
products = (from p in products
where p.Sizeable.Equals(true)
select p).ToArray();
}

if (cbFilterAviOnly.Checked) {
foreach (ShowProduct p in products)
p.Videos = (from v in p.Videos
where v.FileType.Equals("AVI",
StringComparison.CurrentCultureIgnoreCase)
select v).ToArray();
}

gvProducts.DataSource = products;
gvProducts.DataBind();
}

If you see the error, and understand why it happens, congratulations!  If you are like me you noticed that when this runs, and the user filters AVI only, all works as expected.  When they remove the filter however, the non-AVI files do not return.  More confusing is that if they filtered Sizeable products only, and the removed the filter, the missing product did return!  What's going on?

        ShowProduct[] products = Products;

This made a shallow copy of the array in Session.  Each ShowProduct was copied, but the Videos array was a reference.  When I changed the Videos array in the shallow copy, it changed the object in Session because both Session and my copy pointed at the same location in memory.

I searched around for a bit to find a way to solve my problem; i.e. how to make a deep copy in C#/.Net.  There is a method System.FantasyFramework.DeepCopy that will take any object type and return a deep copy of that object back, but I can't get my Visual Studio to find the dll for System.FantasyFramework in the GAC.  Many people have this issue as well, and resort to implementing ICloneable on their object so that the object deep copies itself instead of shallow copies or write an extension method to deep copy the Array class.  I however opted for the quick fix below:

    public ShowProduct[] Products { 
get {
if (Session["ShowProducts"] == null)
return null;

Object result = null;
using (MemoryStream ms = new MemoryStream()) {
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, Session["ShowProducts"]);
ms.Seek(0, SeekOrigin.Begin);
result = bf.Deserialize(ms);
ms.Close();
}

return (ShowProduct[])result;

}
set {
Session["Products"] = value;
}
}

When accessed, the property loads the shallow copy from Session, then Serializes it to memory, then load it's back and returns the result.  This "washes" away any references and makes a full deep copy of the array.  (You do remember the part about not needing this to scale right?  Cool, do don't this on your public website's homepage).  This is also a case where good TDD practices have benefits beyond making it easier to write unit tests.

As for the end of the bar joke, I'm afraid I don't have a punch-line yet.  I think it will have something to do with Session drinking only water, while ForEach and ShallowCopy see who can do the most shots of tequila - until Session falls over drunk.


Posted 04-14-2008 2:12 PM by Michael C. Neel
Filed under: , ,

[Advertisement]

About The CodeBetter.Com Blog Network
CodeBetter.Com FAQ

Our Mission

Advertisers should contact Brendan

Subscribe
Google Reader or Homepage

del.icio.us CodeBetter.com Latest Items
Add to My Yahoo!
Subscribe with Bloglines
Subscribe in NewsGator Online
Subscribe with myFeedster
Add to My AOL
Furl CodeBetter.com Latest Items
Subscribe in Rojo

Member Projects
DimeCasts.Net - Derik Whittaker

Friends of Devlicio.us
Red-Gate Tools For SQL and .NET

NDepend

SlickEdit
 
SmartInspect .NET Logging
NGEDIT: ViEmu and Codekana
LiteAccounting.Com
DevExpress
Fixx
NHibernate Profiler
Unfuddle
Balsamiq Mockups
Scrumy
JetBrains - ReSharper
Umbraco
NServiceBus
RavenDb
Web Sequence Diagrams
Ducksboard<-- NEW Friend!

 



Site Copyright © 2007 CodeBetter.Com
Content Copyright Individual Bloggers

 

Community Server (Commercial Edition)