Bookmark and Share

More On Structs

by KodefuGuru 8. June 2009 12:07

On Friday, I wrote a post that listed guidelines for when to use a struct rather than a class. A commenter of that blog entry asked me to elaborate on the 16-byte rule.

There is a special class in the .NET framework called ValueType. You can't actually inherit from this class nor can you use it as a generic constraint, but you can use it for variable and parameter declarations when you don't want to accept a reference type. There are two type of constructs that inherit from this class: structs and enums.

        static void Main(string[] args)
        {
            
//It will accept structs
            ParameterTest(0);
            
//It will accept enums
            ParameterTest(TypeCode.Boolean);
            
//It won't accept reference types
            ParameterTest("This line doesn't work");
        }

        
static void ParameterTest(ValueType value)
        {
            
Console.WriteLine(value);
        }

Unlike other objects (referred to as reference types), value types go on the stack rather than the heap. The benefits of using the stack are that operations on it are faster, the disadvantage is that there's less memory. Despite the shortage of memory, it isn't going to be an issue the majority of the time unless you're creating tons of these objects. What is an issue is the immutable nature of value types. Every time a modification is made to the object, it creates a new one. Every time you pass the object to a method, it creates a new one. With a large struct, this can be an issue with the default 1 MB you have for the stack. It's also likely that 32-bit processors' operations can handle 16 bytes of memory better than larger sizes, but I lack the expertise on processors to definitively make this claim. If this is the case, 64-bit processors will better handle larger structs.

I am opposed to creating structs with reference type members, and for this reason I'm opposed to creating structs wth string members. Reference members indicate that the value type doesn't really represent a "value." However, if you were to include one it would take up as much space as the reference to the heap takes up. In the case of the Address example in the prior post, the struct size would be 20 bytes - 4 bytes per string on a 32-bit processor.

If you include an mutable reference type as a member on a struct, you run into another problem. The rules of mutability no longer apply, as the reference will still refer to the same memory location.

    class Program
    {
        
static void Main(string[] args)
        {
            
Test test = new Test(1);
            Change(test);

            
//Writes 9 to the screen
            Console.WriteLine(test.ReferenceType.Number);
        }

        
static void Change(Test test)
        {
            test.ReferenceType.Number = 9;
        }
    }

    
struct Test
    {
        
private ReferenceType referenceType;

        
public ReferenceType ReferenceType
        {
            
get { return referenceType; }
            
set { referenceType = value; }
        }

        
public Test(int a)
        {
            referenceType =
new ReferenceType();
            referenceType.Number = a;
        }
    }

    
class ReferenceType
    {
        
public int Number { get; set; }
    }

In this example, the constructor had originally set ReferenceType.Number to 1. The method Change() sets ReferenceType.Number to 9. Since that ReferenceType is a reference, the memory location on the heap has been modified. Therefore, the change will be reflected in the Console.WriteLine() statement. Despite being a value type and the variable in Change() being a copy of the variable in Main(), the semantics of Test have changed to be partly mutable.

Tags:

Kodefu

Bookmark and Share

Stop Using Structs Everywhere

by KodefuGuru 5. June 2009 18:15

One thing that really gets under my skin is the overuse of structs. I hear arguments such as "structs are simple", "I don't have to instantiate it", or my personal favorite, "this is just a record." A struct can be complex, not instantiating something is not a good excuse, and structs are not "records" (usually this comes from Delphi programmers).

A struct should represent a single value. If it doesn't represent a single value, it shouldn't be a struct. That's the general guideline I follow when deciding whether or not to use a struct. In most cases, you should define a class.

Here is the criteria for making a struct:

  1. Does it represent a single value?
  2. Will the instance size be under 16 bytes?
  3. Should it be immutable (modifications actually make a new copy in memory, forcing you to pass by ref to methods)?
  4. Will this rarely need to be boxed (cast to an object)?
  5. Will it usually be short-lived?
  6. Will it mostly be embedded in other objects?

If you can answer yes to all those questions, then you may indeed have a struct on your hands.

This should be a struct:

   public struct Coordinate
   {
      
private decimal latitude;
      
private decimal longitude;

      
public Coordinate(decimal latitude, decimal longitude)
      {
        
this.latitude = latitude;
        
this.longitude = longitude;
      }

      
public decimal Latitude
      {
        
get
        {
            
return latitude;
        }
        
set
        {
            
this.latitude = value;
        }
      }

      
public decimal Longitude
      {
        
get
        {
            
return longitude;
        }
        
set
        {
            
this.longitude = value;
        }
      }
   }

A coordinate is a well-defined value, it's small, and there's no problem making it immutable.

This should not be a struct:

    public class Address
    {
        
public string Street { get; set; }
        
public string City { get; set; }
        
public string State { get; set; }
        
public string Country { get; set; }
        
public string ZipCode { get; set; }

        
public Address(string street, string city, string state, string country, string zipCode)
        {
            Street = street;
            City = city;
            State = state;
            Country = country;
            ZipCode = zipCode;
        }
    }

Although one could argue that an address is a well-defined value (I'd argue that it has too many composite parts), it is clearly going to be larger than 16-bytes. In fact, if you have even one string field, it most likely should be a class anyway.

Note that these are simple examples. Over the course of my career I've seen huge classes that are inexplicably defined as structs. Rarely have I seen a class that should have been defined as a struct.

Tags: , ,

Kodefu

KodefuGuru.GetInfo()

Chris Eargle
LinkedIn Twitter Technorati Facebook

Chris Eargle
C# MVP, INETA Community Champion


MVP - Visual C#

 

INETA Community Champions
Friend of RedGate
Telerik .NET Ninja
Community blogs & blog posts

I am a #52er

I have joined Anti-IF Campaign


World Map

Tag cloud

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2010