A struct is a value type. When a struct is created, the variable to which the struct is assigned holds the struct's actual data. When the struct is assigned to a new variable, it is copied. The new variable and the original variable therefore contain two separate copies of the same data. Changes made to one copy do not affect the other copy.

Value type variables directly contain their values, which means that the memory is allocated inline in whatever context the variable is declared. There is no separate heap allocation or garbage collection overhead for value-type variables.

There are two categories of value types: struct and enum.

The built-in numeric types are structs, and they have properties and methods that you can access:

// Static method on type Byte.  
byte b = Byte.MaxValue;

But you declare and assign values to them as if they were simple non-aggregate types:

byte num = 0xA;  
int i = 5;  
char c = 'Z';

Value types are sealed, which means, for example, that you cannot derive a type from Int32, and you cannot define a struct to inherit from any user-defined class or struct because a struct can only inherit from ValueType. However, a struct can implement one or more interfaces. You can cast a struct type to an interface type; this causes a boxing operation to wrap the struct inside a reference type object on the managed heap. Boxing operations occur when you pass a value type to a method that takes an Object as an input parameter. For more information, see Boxing and Unboxing.

You use the struct keyword to create your own custom value types. Typically, a struct is used as a container for a small set of related variables, as shown in the following example:

public struct Coords
    public int x, y;

    public Coords(int p1, int p2)
        x = p1;
        y = p2;

For more information about value types in the .NET Framework, see Common Type System.

Structs share most of the same syntax as classes, although structs are more limited than classes:

  • Within a struct declaration, fields cannot be initialized unless they are declared as const or static.

  • A struct cannot declare a parameterless constructor (a constructor without parameters) or a finalizer.

  • Structs are copied on assignment. When a struct is assigned to a new variable, all the data is copied, and any modification to the new copy does not change the data for the original copy. This is important to remember when working with collections of value types such as Dictionary<string, myStruct>.

  • Structs are value types and classes are reference types.

  • Unlike classes, structs can be instantiated without using a new operator.

  • Structs can declare constructors that have parameters.

  • A struct cannot inherit from another struct or class, and it cannot be the base of a class. All structs inherit directly from ValueType, which inherits from Object.

  • A struct can implement interfaces.

Literal values

In C#, literal values receive a type from the compiler. You can specify how a numeric literal should be typed by appending a letter to the end of the number. For example, to specify that the value 4.56 should be treated as a float, append an "f" or "F" after the number: 4.56f. If no letter is appended, the compiler will infer the double type for the literal. For more information about which types can be specified with letter suffixes, see the reference pages for individual types in Value Types.

Because literals are typed, and all types derive ultimately from Object, you can write and compile code such as the following:

string s = "The answer is " + 5.ToString();
// Outputs: "The answer is 5"

Type type = 12345.GetType();
// Outputs: "System.Int32"

var x = 123_456;
string s2 = "I can use an underscore as a digit separator: " + x;
// Outputs: "I can use an underscore as a digit separator: 123456"

var b = 0b1010_1011_1100_1110_1111;
string s3 = "I can specify bit patterns: " + b.ToString();
// Outputs: "I can specify bit patterns: 703727"

The last two examples demonstrate language features introduced in C# 7.0. The first allows you to use an underscore character as a digit separator inside numeric literals. You can put them wherever you want between digits to improve readability. They have no effect on the value.

The second demonstrates binary literals, which allow you to specify bit patterns directly instead of using hexadecimal notation.

Nullable types

Ordinary value types cannot have a value of null. However, you can create nullable value types by affixing a ? after the type. For example, int? is an int type that can also have the value null. In the CTS, nullable types are instances of the generic struct type Nullable<T>. Nullable types are especially useful when you are passing data to and from databases in which numeric values might be null. For more information, see Nullable Types (C# Programming Guide).

See also