Sessions and Classes

Any programmer knows that logic errors are among the most difficult to troubleshoot. I was recently working with a developer who was encountering an obscure logic bug concerning session variables, and the case raised an interesting (and important) point regarding ASP.NET session state.

Consider the following code in page1.aspx.

public partial class Page1 : System.Web.UI.Page
{

    protected ArrayList al;

    protected void Page_Load(object sender, EventArgs e)
    {
        al = new ArrayList();
        al.Add("Apples");
        al.Add("Oranges");
        al.Add("Bananas");

        Session["list"] = al;

        Response.Redirect("page2.aspx");
    }

}

In this code, I create an ArrayList and then populate it with a few types of fruit. I then save that ArrayList to session and redirect to page2.aspx. Let’s look at several different code examples for page2.aspx and discuss how the session variable created in page1.aspx might be inadvertently changed.

First, consider this code snippet for page2.aspx.

public partial class Page2 : System.Web.UI.Page
{

    protected ArrayList al;
    protected ArrayList al1;
    protected void Page_Load(object sender, EventArgs e)
    {
        al = (ArrayList)Session["list"];
        al[2] = "Kiwi";

        al1 = (ArrayList)Session["list"];
        Response.Write(al1[2]);
    }
}

When page2.aspx loads, what is written to the browser window? You may think that you’d see “Oranges”, but in fact, you’d see “Kiwi” written to the screen. Why? When you store an instance of a reference type (an ArrayList, in this case) in session and then assign a variable to the session variable, what you are doing is saying “Point my variable to the address of the session variable.” Therefore, if you manipulate the member variable that was assigned the session variable, you are also directly manipulating the session variable. In other words, it is not necessary for me to save the modified ArrayList back to session in page2.aspx.

Here’s another snippet of code for page2.aspx.

public partial class Page2 : System.Web.UI.Page
{

    protected ArrayList al;
    protected void Page_Load(object sender, EventArgs e)
    {
        al = (ArrayList)Session["list"];
        al = new ArrayList();

        Response.Write(((ArrayList)Session["list"])[0]);
    }
}

When this code runs, what happens? If you said that you’d see “Apples”, you’re right. Obviously, assigning the al variable to a new instance of ArrayList doesn’t impact the session at all. It simply says “Create a new ArrayList and point al at the new ArrayList instead of to the session variable.”

Now consider the following code for page2.aspx.

public partial class Page2 : System.Web.UI.Page
{

    protected ArrayList al;
    protected ArrayList al2;
    protected void Page_Load(object sender, EventArgs e)
    {
        al = (ArrayList)Session["list"];
        al2 = al;

        al2.Clear();

        Response.Write(((ArrayList)Session["list"])[0]);
    }
}

When this code runs, what do you see? If you said that you’d see an exception, you’re right. This code will produce an ArgumentOutOfRangeException because the session variable, al, and al2 all point to the same object. When you call Clear() on al2, it clears the session variable.

Obviously, this situation doesn’t apply to primitive types such as int or to value types. It also doesn’t apply to string even though string is a reference type. Why? Because the equality operators for string are designed to compare the value of the string and not the string itself. This situation also doesn’t apply to structs because a struct is a value type.