When should I use a struct rather than a class in C#?

Viewed 330879

When should you use struct and not class in C#? My conceptual model is that structs are used in times when the item is merely a collection of value types. A way to logically hold them all together into a cohesive whole.

I came across these rules here:

  • A struct should represent a single value.
  • A struct should have a memory footprint less than 16 bytes.
  • A struct should not be changed after creation.

Do these rules work? What does a struct mean semantically?

31 Answers

Whenever you:

  1. don't need polymorphism,
  2. want value semantics, and
  3. want to avoid heap allocation and the associated garbage collection overhead.

The caveat, however, is that structs (arbitrarily large) are more expensive to pass around than class references (usually one machine word), so classes could end up being faster in practice.

I do not agree with the rules given in the original post. Here are my rules:

  1. You use structs for performance when stored in arrays. (see also When are structs the answer?)

  2. You need them in code passing structured data to/from C/C++

  3. Do not use structs unless you need them:

    • They behave different from "normal objects" (reference types) under assignment and when passing as arguments, which can lead to unexpected behavior; this is particularly dangerous if the person looking at the code does not know they are dealing with a struct.
    • They cannot be inherited.
    • Passing structs as arguments is more expensive than classes.

Use a struct when you want value semantics as opposed to reference semantics.

If you need reference semantics you need a class not a struct.

Structs are good for atomic representation of data, where the said data can be copied multiple times by the code. Cloning an object is in general more expensive than copying a struct, as it involves allocating the memory, running the constructor and deallocating/garbage collection when done with it.

First: Interop scenarios or when you need to specify the memory layout

Second: When the data is almost the same size as a reference pointer anyway.

You need to use a "struct" in situations where you want to explicitly specify memory layout using the StructLayoutAttribute - typically for PInvoke.

Edit: Comment points out that you can use class or struct with StructLayoutAttribute and that is certainly true. In practice, you would typically use a struct - it is allocated on the stack vs the heap which makes sense if you are just passing an argument to an unmanaged method call.

I use structs for packing or unpacking any sort of binary communication format. That includes reading or writing to disk, DirectX vertex lists, network protocols, or dealing with encrypted/compressed data.

The three guidelines you list haven't been useful for me in this context. When I need to write out four hundred bytes of stuff in a Particular Order, I'm gonna define a four-hundred-byte struct, and I'm gonna fill it with whatever unrelated values it's supposed to have, and I'm going to set it up whatever way makes the most sense at the time. (Okay, four hundred bytes would be pretty strange-- but back when I was writing Excel files for a living, I was dealing with structs of up to about forty bytes all over, because that's how big some of the BIFF records ARE.)

With the exception of the valuetypes that are used directly by the runtime and various others for PInvoke purposes, you should only use valuetypes in 2 scenarios.

  1. When you need copy semantics.
  2. When you need automatic initialization, normally in arrays of these types.

MYTH #1: STRUCTS ARE LIGHTWEIGHT CLASSES

This myth comes in a variety of forms. Some people believe that value types can’t or shouldn’t have methods or other significant behavior—they should be used as simple data transfer types, with just public fields or simple properties. The DateTime type is a good counterexample to this: it makes sense for it to be a value type, in terms of being a fundamental unit like a number or a character, and it also makes sense for it to be able to perform calculations based on its value. Looking at things from the other direction, data transfer types should often be reference types anyway—the decision should be based on the desired value or reference type semantics, not the simplicity of the type. Other people believe that value types are “lighter” than reference types in terms of performance. The truth is that in some cases value types are more performant— they don’t require garbage collection unless they’re boxed, don’t have the type identification overhead, and don’t require dereferencing, for example. But in other ways, reference types are more performant—parameter passing, assigning values to variables, returning values, and similar operations only require 4 or 8 bytes to becopied (depending on whether you’re running the 32-bit or 64-bit CLR) rather than copying all the data. Imagine if ArrayList were somehow a “pure” value type, and passing an ArrayList expression to a method involved copying all its data! In almost all cases, performance isn’t really determined by this sort of decision anyway. Bottlenecks are almost never where you think they’ll be, and before you make a design decision based on performance, you should measure the different options. It’s worth noting that the combination of the two beliefs doesn’t work either. It doesn’t matter how many methods a type has (whether it’s a class or a struct)—the memory taken per instance isn’t affected. (There’s a cost in terms of the memory taken up for the code itself, but that’s incurred once rather than for each instance.)

MYTH #2: REFERENCE TYPES LIVE ON THE HEAP; VALUE TYPES LIVE ON THE STACK

This one is often caused by laziness on the part of the person repeating it. The first part is correct—an instance of a reference type is always created on the heap. It’s the second part that causes problems. As I’ve already noted, a variable’s value lives wherever it’s declared, so if you have a class with an instance variable of type int, that variable’s value for any given object will always be where the rest of the data for the object is—on the heap. Only local variables (variables declared within methods) and method parameters live on the stack. In C# 2 and later, even some local variables don’t really live on the stack, as you’ll see when we look at anonymous methods in chapter 5. ARE THESE CONCEPTS RELEVANT NOW? It’s arguable that if you’re writing managed code, you should let the runtime worry about how memory is best used. Indeed, the language specification makes no guarantees about what lives where; a future runtime may be able to create some objects on the stack if it knows it can get away with it, or the C# compiler could generate code that hardly uses the stack at all. The next myth is usually just a terminology issue.

MYTH #3: OBJECTS ARE PASSED BY REFERENCE IN C# BY DEFAULT

This is probably the most widely propagated myth. Again, the people who make this claim often (though not always) know how C# actually behaves, but they don’t know what “pass by reference” really means. Unfortunately, this is confusing for people who do know what it means. The formal definition of pass by reference is relatively complicated, involving l-values and similar computer-science terminology, but the important thing is that if you pass a variable by reference, the method you’re calling can change the value of the caller’s variable by changing its parameter value. Now, remember that the value of a reference type variable is the reference, not the object itself. You can change the contents of the object that a parameter refers to without the parameter itself being passed by reference. For instance, the following method changes the contents of the StringBuilder object in question, but the caller’s expression will still refer to the same object as before:

void AppendHello(StringBuilder builder)
{
    builder.Append("hello");
}

When this method is called, the parameter value (a reference to a StringBuilder) is passed by value. If you were to change the value of the builder variable within the method—for example, with the statement builder = null;—that change wouldn’t be seen by the caller, contrary to the myth. It’s interesting to note that not only is the “by reference” bit of the myth inaccurate, but so is the “objects are passed” bit. Objects themselves are never passed, either by reference or by value. When a reference type is involved, either the variable is passed by reference or the value of the argument (the reference) is passed by value. Aside from anything else, this answers the question of what happens when null is used as a by-value argument—if objects were being passed around, that would cause issues, as there wouldn’t be an object to pass! Instead, the null reference is passed by value in the same way as any other reference would be. If this quick explanation has left you bewildered, you might want to look at my article, “Parameter passing in C#,” (http://mng.bz/otVt), which goes into much more detail. These myths aren’t the only ones around. Boxing and unboxing come in for their fair share of misunderstanding, which I’ll try to clear up next.

Reference: C# in Depth 3rd Edition by Jon Skeet

Nah - I don't entirely agree with the rules. They are good guidelines to consider with performance and standardization, but not in light of the possibilities.

As you can see in the responses, there are a lot of creative ways to use them. So, these guidelines need to just be that, always for the sake of performance and efficiency.

In this case, I use classes to represent real world objects in their larger form, I use structs to represent smaller objects that have more exact uses. The way you said it, "a more cohesive whole." The keyword being cohesive. The classes will be more object oriented elements, while structs can have some of those characteristics, though on a smaller scale. IMO.

I use them a lot in Treeview and Listview tags where common static attributes can be accessed very quickly. I have always struggled to get this info another way. For example, in my database applications, I use a Treeview where I have Tables, SPs, Functions, or any other objects. I create and populate my struct, put it in the tag, pull it out, get the data of the selection and so forth. I wouldn't do this with a class!

I do try and keep them small, use them in single instance situations, and keep them from changing. It's prudent to be aware of memory, allocation, and performance. And testing is so necessary.

The C# struct is a lightweight alternative to a class. It can do almost the same as a class, but it's less "expensive" to use a struct rather than a class. The reason for this is a bit technical, but to sum up, new instances of a class is placed on the heap, where newly instantiated structs are placed on the stack. Furthermore, you are not dealing with references to structs, like with classes, but instead you are working directly with the struct instance. This also means that when you pass a struct to a function, it is by value, instead of as a reference. There is more about this in the chapter about function parameters.

So, you should use structs when you wish to represent more simple data structures, and especially if you know that you will be instantiating lots of them. There are lots of examples in the .NET framework, where Microsoft has used structs instead of classes, for instance the Point, Rectangle and Color struct.

I think a good first approximation is "never".

I think a good second approximation is "never".

If you are desperate for perf, consider them, but then always measure.

Following are the rules defined at Microsoft website:

✔️ CONSIDER defining a struct instead of a class if instances of the type are small and commonly short-lived or are commonly embedded in other objects.

❌ AVOID defining a struct unless the type has all of the following characteristics:

It logically represents a single value, similar to primitive types (int, double, etc.).

It has an instance size under 16 bytes.

It is immutable.

It will not have to be boxed frequently.

for further reading

Let me add another aspect besides the commonly cited performance difference and that is the intention revealing usage of default values.

Do not use a struct if the default values of its fields do not represent a sensible default value of the modeled concept.

Eg.

  • A Color or a Point makes sense even if all of their fields are set to their default values. RGB 0,0,0 is a perfectly good color and so is (0,0) as a Point in 2D.
  • But an Address or a PersonName does not have a sensible default value. I mean can you make sense of a PersonName that has FirstName=null and LastName=null?

If you implement a concept with a class then you can enforce certain invariants, eg. that a person must have a first name and a last name. But with a struct it is always possible to create an instance with all of its fields set to their default values.

So when modeling a concept that has no sensible default value prefer a class. The users of your class will understand that null means that a PersonName is not specified but they will be confused if you hand them a PersonName struct instance with all of its properties set to null.

(Usual disclaimer: performance considerations may override this advice. If you have performance concerns always measure before deciding on a solution. Try BenchmarkDotNet it's awsome!)

I rarely use a struct for things. But that's just me. It depends whether I need the object to be nullable or not.

As stated in other answers, I use classes for real-world objects. I also have the mindset of structs are used for storing small amounts of data.

✔️ CONSIDER Struct Usage

  1. Create an object or don't need to create the object (directly you can assign values, it creates object)
  2. Need Speed or performance improvement
  3. No Need Constructors and Destractors (Static Contractor available)
  4. No Need for class Inheritance, but Interfaces are acceptable
  5. Small workload object work, If it goes high, memory issue will raise
  6. You can't default values for variables.
  7. Struct also available methods, event, static constructors, variable, etc
  8. Less workload in GC
  9. No need for reference types, only values type( every time you create a new object)
  10. No Immutable Object (string is Immutable object because any operation don it returns any every time new string without changing the original)

Classes are best suited for grouping together complex actions and data that will change throughout a program; structs are a better choice for simple objects and data that will remain constant for the most part. Besides their uses, they are fundamentally different in one key area—that is, how they are passed or assigned between variables. Classes are reference types, meaning that they are passed by reference; structs are value types, meaning that they are passed by value.

  • be careful using class. If you have some game objects that refer to the same memory, modifying one will modify the others.

When a struct object is created, all of its data is stored in its corresponding variable with no references or connections to its memory location. This makes structs useful for creating objects that need to be copied quickly and efficiently, while still retaining their separate identities.

ExampleStruct struct1= new ExampleStruct()
ExampleStruct struct2= struct1

modifying struct2 wont affect struct1.

  • Basically, structs are created to increase performance. But, sometimes structs may be slower because of all the copying involved. If your struct has lots of variables that need to be copied converting it to a class and just passing references around may be faster

  • If you have an array of structs, the array itself is an object on the heap and struct values are contained in the array. So garbage collector only has one object to consider. If the array goes out of the scope, the garbage collector can deallocate all the structs inside the array in one step. If any other part of your code is using structs from this array, since structs are copied we can safely deallocate the array itself and its contents.

  • If you have an array of objects, the array itself and each object in the array are separe objects on the heap. Each object could be stored in a totally different part of the heap and another part of your code might have references to those objects. So when our array goes out of scope, we cannot deallocate the array right away. Because the garbage collector has to consider each object individually and make sure there are no references to each object before de-allocating them.

Related