GOTCHA 2 struct and class differ in behavior


GOTCHA #2 struct and class differ in behavior

In C++, you can create an object on the stack or on the heap. When you use the new keyword, you create the object on the heap. In Java, you can only create objects on the heap; primitive built-in types are created on the stack, unless they are embedded within an object. Also, you can't use the new keyword on primitive built-in types.

.NET behaves more like Java than C++. An object is created on the heap if it is a reference type. If it is a value type, it is created on the stack, unless it is embedded within an object. Whether an object is a value type or reference type depends on how it is defined. If it is defined using the class keyword, it is a reference type. If it is defined with the struct keyword in C# or Structure in VB.NET, it's a value type. Even though you are using the same new keyword in the syntax, an instance of a reference type is created on the managed heap but an instance of a value type is created on the stack. This leads to some confusion when looking at code. Specifically, the effect of an assignment statement varies between structures (value types) and classes (reference types). This is illustrated in Example 1-3. The potentially troublesome assignment statements are highlighted.

Example 1-3. Assignment of reference type

C# (ValueReferenceTypes)

 //AType.cs using System; namespace ValTypeRefTypeAssignment {     public class AType     {         private int aField;         public int TheValue         {             get { return aField; }             set { aField = value; }         }     } } //Test.cs using System; namespace ValTypeRefTypeAssignment {     class Test     {         [STAThread]         static void Main(string[] args)         {             AType firstInstance = new AType();             firstInstance.TheValue = 2;             AType secondInstance = new AType();             secondInstance.TheValue = 3;             Console.WriteLine("The values are {0} and {1}",                 firstInstance.TheValue,                 secondInstance.TheValue);             firstInstance = secondInstance; // Line A             Console.Write("Values after assignment ");             Console.WriteLine("are {0} and {1}",                 firstInstance.TheValue,                 secondInstance.TheValue);             secondInstance.TheValue = 4;             Console.Write("Values after modifying TheValue ");             Console.Write("in secondInstance are ");             Console.WriteLine("{0} and {1}",                 firstInstance.TheValue,                 secondInstance.TheValue);         }     } } 

VB.NET (ValueReferenceTypes)

 'AType.vb Public Class AType     Private aField As Integer     Public Property TheValue() As Integer         Get             Return aField         End Get         Set(ByVal Value As Integer)             aField = Value         End Set     End Property End Class 'Test.vb Public Class Test     Public Shared Sub Main()         Dim firstInstance As New AType         firstInstance.TheValue = 2         Dim secondInstance As New AType         secondInstance.TheValue = 3         Console.WriteLine("The values are {0} and {1}", _             firstInstance.TheValue, _             secondInstance.TheValue)         firstInstance = secondInstance ' Line A         Console.Write("Values after assignment ")         Console.WriteLine("are {0} and {1}", _             firstInstance.TheValue, _             secondInstance.TheValue)         secondInstance.TheValue = 4         Console.Write("Values after modifying TheValue ")         Console.Write("in secondInstance are ")         Console.WriteLine("{0} and {1}", _             firstInstance.TheValue, _             secondInstance.TheValue)     End Sub End Class 

The output produced by the above code is shown in Figure 1-1.

Figure 1-1. Output from Example 1-3


In the assignment statement:

 firstInstance = secondInstance 

you are modifying the reference firstInstance. The effect of that statement is shown in Figure 1-2.

Figure 1-2. Effect of assignment on reference type


Therefore, when you change secondInstance.TheValue, you also change firstInstance.TheValue, since firstInstance and secondInstance now refer to the same object.

Let's make just one change. Let's modify AType from a class to a struct (Structure in VB.NET). This is the only change. There is no other change to the Test class or its Main() method. The output produced by the program now is shown in Figure 1-3.

Figure 1-3. Output after modification of class to struct/Structure in Example 1-3


This time, the assignment statement at Line A (firstInstance = secondInstance) changes the value stored in the firstInstance structure. The effect of that statement is shown in Figure 1-4.

Figure 1-4. Effect of assignment on value type


Therefore, changing secondInstance.TheValue has no effect on firstInstance.TheValue, since in this case they are still different objects. The assignment made a bitwise copy.

The effect of an assignment statement differs for the two types of object. In the case of the reference type (where AType is declared as a class), firstInstance refers to the object on the heap. Therefore, after the assignment statement, firstInstance and secondInstance end up referring to the same instance on the heap. This is very similar to pointer manipulation in C++. However, when AType is declared as a struct/Structure, the firstInstance becomes a variable local to the stack representing an instance of the value type. Therefore, the assignment statement copies the memory content from secondInstance to firstInstance.

Given this confusion, is it worth using a value type? Well, since the core CLS types (such as System.Int32, System.Char, and System.Double), are value types, they must have their uses and benefits. What are they?

Value types are allocated on the stack. They are passed by value as method parameters (unless tagged as ref/ByRef), so the called method cannot change them inadvertently. This argues for keeping value types small: by-value parameters are copied, and copying large objects can be expensive.

Another good candidate for value types is objects used to represent the internal state of a larger object (and not exposed to users of that object).

You need to exercise caution when using value types with collections in .NET 1.1 (non-generic collections). See Gotcha #9, "Typeless ArrayList isn't type-safe."

I recommend that you use a class unless you have a specific need for a struct/Structure, such as when:

  • The object is really small and you want to eliminate the overhead of a reference

  • You intend to create a large array of these types and do not want the overhead of constructor calls for each object in the array

IN A NUTSHELL

Assignment may lead to confusion because you can't quite figure out if you are using a value type or a reference type merely by looking at the code. Do not make any assumptions when you see an assignment statementexplore the object further to make sure you understand how the assignment will behave. Also, limit the use of value types, as much as possible, to small objects.

SEE ALSO

Gotcha #1, "Type alias size doesn't match what you're familiar with," Gotcha #3, "Returning value types from a method/property is risky," Gotcha #4, "You can't force calls to your value-type constructors," and Gotcha #9, "Typeless ArrayList isn't type-safe."



    .NET Gotachas
    .NET Gotachas
    ISBN: N/A
    EAN: N/A
    Year: 2005
    Pages: 126

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net