Monday, April 11, 2011

.NET cache stores only a reference -- and what to do about it

.NET provides the System.Web.Caching.Cache object as a way to persistently store data. This is useful, for example, if you retrieve some data from a database and want to refer to it repeatedly, perhaps from several different pages, without the overhead of accessing the database again each time.

When I attempted to store an instance of a class I had created with several members -- I discovered a quirk of the Cache object: adding such an object to the cache seems to store a reference to the object, not a copy of the object. An example will make this clear.

// define a class with some members
class Animal
{
public string species;
public string name;

public Animal(string s, string n)
{
species = s;
name = n;
}
}

// create a dog named Spot
Animal animal = new Animal("dog", "Spot");

// store Spot in the cache
HttpContext.Current.Cache.Insert("MyAnimal", animal);

// change Spot's name to Rover
animal.name = "Rover";

// get Spot from the cache
Animal animal2 = (Animal)HttpContext.Current.Cache.Get("MyAnimal");

// this is the cached object; the name should be Spot, but instead it's Rover ??!!
Label1.Text = animal2.name;

I expected the above code to set the label to Spot, but it actually sets it to Rover. Cache. Insert appears to store a reference to the object. I have yet to find anyplace this is mentioned in Microsoft's documentation. Kudos to Martin Bakiev of Penn State University for figuring this out.

Here's one way to get around the problem. Add another constructor to the Animal class. Use to create a copy of the object, and then store that copy in the cache. This has the desired effect, setting the label to Spot.


// define a class with some members
class Animal
{
public string species;
public string name;

public Animal(string s, string n)
{
species = s;
name = n;
}

public Animal(Animal a)
{
species = a.species;
name = a.name;
}
}

// create a dog named Spot
Animal animal = new Animal("dog", "Spot");

// store Spot in the cache
//HttpContext.Current.Cache.Insert("MyAnimal", animal);
HttpContext.Current.Cache.Insert("MyAnimal", new Animal(animal));

// change Spot's name to Rover
animal.name = "Rover";

// get Spot from the cache
Animal animal2 = (Animal)HttpContext.Current.Cache.Get("MyAnimal");

// this is the cached object; the name should be Spot, but instead it's Rover ??!!
Label1.Text = animal2.name;

That avoids the problem, but it's not very convenient if you want to store instances of many different classes in the cache; you'd need to create a new constructor for each class. I found a better solution: I wrote methods that use the .NET BinaryFormatter class to serialize an object on its way into the cache, and deserialize it on the way back out. I may be able to share that code in a future post.

No comments:

Post a Comment