Skip to content

C# Assignment Operator Struct

A struct in the C programming language (and many derivatives) is a composite data type (or record) declaration that defines a physically grouped list of variables to be placed under one name in a block of memory, allowing the different variables to be accessed via a single pointer, or the struct declared name which returns the same address. The struct can contain many other complex and simple data types in an association, so is a natural organizing type for records like the mixed data types in lists of directory entries reading a hard drive (file length, name, extension, physical (cylinder, disk, head indexes) address, etc.), or other mixed record type (patient names, address, telephone... insurance codes, balance, etc.).

The C struct directly references a contiguous block of physical memory, usually delimited (sized) by word-length boundaries. It corresponds to the similarly named feature available in some assemblers for Intel processors. Language implementations that could utilize half-word or byte boundaries (giving denser packing, using less memory) were considered advanced in the mid-eighties. Being a block of contiguous memory, each field within a struct located at a certain fixed offset from the start. As an illustration, many BASIC interpreters once fielded a string data struct organization with one value recording string length, one indexing (cursor value of) the previous line, one pointing to the string data.

Because the contents of a struct are stored in contiguous memory, the sizeof operator must be used to get the number of bytes needed to store a particular type of struct, just as it can be used for primitives. The alignment of particular fields in the struct (with respect to word boundaries) is implementation-specific and may include padding, although modern compilers typically support the directive, which changes the size in bytes used for alignment.[1]

In the C++ language, a struct is identical to a C++ class but a difference in the default visibility exists: class members are by default private, whereas struct members are by default public.

In other languages[edit]

The struct data type in C was derived from the ALGOL 68 struct data type.[2]

Like its C counterpart, the struct data type in C# (Structure in Visual Basic .NET) is similar to a class. The biggest difference between a struct and a class in these languages is that when a struct is passed as an argument to a function, any modifications to the struct in that function will not be reflected in the original variable (unless pass-by-reference is used).[3]

This differs from C++, where classes or structs can be statically allocated or dynamically allocated either on the stack (similar to C#) or on the heap, with an explicit pointer. In C++, the only difference between a struct and a class is that the members and base classes of a struct are public by default. (A class defined with the keyword has private members and base classes by default.)

Declaration[edit]

The general syntax for a struct declaration in C is:

structtag_name{typemember1;typemember2;/* declare as many members as desired, but the entire structure size must be known to the compiler. */};

Here is optional in some contexts.

Such a declaration may also appear in the context of a typedef declaration of a type alias or the declaration or definition of a variable:

typedefstructtag_name{typemember1;typemember2;}struct_alias;

Often, such entities are better declared separately, as in:

typedefstructtag_namestruct_alias;// These two statements now have the same meaning:// struct tag_name struct_instance;// struct_alias struct_instance;

For example:

structaccount{intaccount_number;char*first_name;char*last_name;floatbalance;};

defines a type, referred to as . To create a new variable of this type, we can write

which has an integer component, accessed by , and a floating-point component, accessed by , as well as the and components. The structure contains all four values, and all four fields may be changed independently.

A pointer to an instance of the "account" structure will point to the memory address of the first variable, "account_number". The total storage required for a object is the sum of the storage requirements of all the fields, plus any internal padding.

Struct initialization[edit]

There are three ways to initialize a structure. For the type

/* Forward declare a type "point" to be a struct. */typedefstructpointpoint;/* Declare the struct with integer members x, y */structpoint{intx;inty;};

C89-style initializers are used when contiguous members may be given.[4]

/* Define a variable p of type point, and initialize its first two members in place */pointp={1,2};

For non contiguous or out of order members list, designated initializer style[5] may be used

/* Define a variable p of type point, and set members using designated initializers*/pointp={.y=2,.x=1};

If an initializer is given or if the object is statically allocated, omitted elements are initialized to 0.[6]

A third way of initializing a structure is to copy the value of an existing object of the same type

/* Define a variable q of type point, and set members to the same values as those of p */pointq=p;

Assignment[edit]

The following assignment of a struct to another struct will copy as one might expect. Depending on the contents, a compiler might use in order to perform this operation.

#include<stdio.h>/* Define a type point to be a struct with integer members x, y */typedefstruct{intx;inty;}point;intmain(void){/* Define a variable p of type point, and initialize all its members inline! */pointp={1,3};/* Define a variable q of type point. Members are uninitialized. */pointq;/* Assign the value of p to q, copies the member values from p into q. */q=p;/* Change the member x of q to have the value of 3 */q.x=3;/* Demonstrate we have a copy and that they are now different. */if(p.x!=q.x)printf("The members are not equal! %d != %d",p.x,q.x);/* Define a variable r of type point. Members are uninitialized. */pointr;/* Assign values using compound literal (ISO C99/supported by GCC > 2.95) */r=(point){1,2};return0;}

Pointers to struct[edit]

Pointers can be used to refer to a by its address. This is particularly useful for passing structs to a function by reference or to refer to another instance of the type as a field. The pointer can be dereferenced just like any other pointer in C, using the operator. There is also a operator in C which dereferences the pointer to struct (left operand) and then accesses the value of a member of the struct (right operand).

structpoint{intx;inty;};structpointmy_point={3,7};structpoint*p=&my_point;/* To declare and define p as a pointer of type struct point, and initialize it with the address of my_point. */(*p).x=8;/* To access the first member of the struct */p->x=8;/* Another way to access the first member of the struct */

C does not allow recursive declaration of ; a can not contain a field that has the type of the itself. But pointers can be used to refer to an instance of it:

typedefstructlist_elementlist_element;structlist_element{pointp;list_element*next;};list_elementel={.p={.x=3,.y=7},};list_elementle={.p={.x=4,.y=5},.next=&el};

Here the instance would contain a with coordinates 3 and 7. Its pointer would be a null pointer since the initializer for that field is omitted. The instance in turn would have its own and its pointer would refer to .

typedef[edit]

Main article: typedef

Typedefs can be used as shortcuts, for example:

typedefstruct{intaccount_number;char*first_name;char*last_name;floatbalance;}account;

Different users have differing preferences; proponents usually claim:

  • shorter to write
  • can simplify more complex type definitions
  • can be used to forward declare a type

As an example, consider a type that defines a pointer to a function that accepts pointers to struct types and returns a pointer to struct:

Without typedef:

structpoint{intx;inty;};structpoint*(*point_compare_func)(structpoint*a,structpoint*b);

With typedef:

typedefstructpointpoint_type;structpoint{intx;inty;};point_type*(*point_compare_func)(point_type*a,point_type*b);

A common naming convention for such a is to append a "" (here ) to the tag name, but such names are reserved by POSIX so such a practice should be avoided. A much easier convention is to use just the same identifier for the tag name and the type name:

typedefstructpointpoint;structpoint{intx;inty;};point*(*point_compare_func)(point*a,point*b);

Without a function that takes function pointer the following code would have to be used. Although valid, it becomes increasingly hard to read.

/* Using the struct point type from before *//* Define a function that returns a pointer to the biggest point, using a function to do the comparison. */structpoint*biggest_point(size_tsize,structpoint*points,structpoint*(*point_compare)(structpoint*a,structpoint*b)){inti;structpoint*biggest=NULL;for(i=0;i<size;i++){biggest=point_compare(biggest,points+i);}returnbiggest;}

Here a second for a function pointer type can be useful

typedefpoint*(*point_compare_func_type)(point*a,point*b);

Now with the two s being used the complexity of the function signature is drastically reduced.

/* Using the struct point type from before and the typedef for the function pointer *//* Define a function that returns a pointer to the biggest point, using a function to do the comparison. */point*biggest_point(size_tsize,point*points,point_compare_func_typepoint_compare){inti;point*biggest=NULL;for(i=0;i<size;i++){biggest=point_compare(biggest,points+i);}returnbiggest;}

However, there are a handful of disadvantages in using them:

  • They pollute the main namespace (see below), however this is easily overcome with prefixing a library name to the type name.
  • Harder to figure out the aliased type (having to scan/grep through code), though most IDEs provide this lookup automatically.
  • Typedefs do not really "hide" anything in a struct or union — members are still accessible (). To really hide struct members, one needs to use 'incompletely-declared' structs.
/* Example for namespace clash */typedefstructaccount{floatbalance;}account;structaccountaccount;/* possible */accountaccount;/* error */

See also[edit]

References[edit]

  1. ^C struct memory layout? - Stack Overflow
  2. ^Ritchie, Dennis M. (March 1993). "The Development of the C Language". ACM SIGPLAN Notices. 28 (3): 201–208. doi:10.1145/155360.155580.  
  3. ^Parameter passing in C#
  4. ^Kelley, Al; Pohl, Ira (2004). A Book On C: Programming in C (Fourth ed.). p. 418. ISBN 0-201-18399-4. 
  5. ^"IBM Linux compilers. Initialization of structures and unions". 
  6. ^"The New C Standard, §6.7.8 Initialization". 
MyStruct some = null; // some == MyStruct(...)

This is a small post about a rather specific construct in C# syntax, permitting to assign "null" to struct-typed variables (which are non-nullable) and treat this specific case as you please.

If you did not come here looking for this particular thing, you may or may not be in a dire need of the post's subject, but explanation may be of interest.

Prehistory

I have stumbled upon the problem with implicitly converting "primitive" values to structs while configuring C# code generation for a Haxe project. While the Haxe code works perfectly fine by itself, working with it from C# currently can be a bit problematic.

Say, you have the following code:

function some(a:String, ?b:Bool):Void

which turns into this upon compilation:

void some(string a, haxe.lang.Null<bool> b)

Aside of the optional parameter no longer being optional (that's a story for another day), doesn't look too suspicious, right? But let's take a look at how you would call this procedure with a single parameter (leaving the second to be null):

some("Hello", new haxe.lang.Null<bool>(default(bool)));

... pretty convenient, huh? Except not really. The thing here is that has two constructors - one taking a single (default) value for "nulled" value, and another one taking two for "non-nulled".

As you can expect, being able to just pass "true", "false", or "null" would be more convenient.

Context

For a simple example, let's take a look at another "nullable type" kind of struct:

struct Null<T> {publicreadonlybool hasValue; publicreadonly T value; publicNull(bool hasValue, T value){this.hasValue = hasValue; this.value = value; }publicoverridestring ToString(){return hasValue ? value.ToString() : "null"; }}

So here we have two fields (containing the value and whether there is one), a nondescript constructor, and a simple ToString override. Now, to implicit conversions:

Implicit conversion from primitive type

This one's easy. You define a "implicit operator" that takes our parameter-type, returns a new struct\object, and basically just calls the constructor accordingly:

publicstaticimplicitoperatorNull<T>(T value){returnnewNull<T>(true, value); }

These shown structures go into the "struct", just in case.

Implicit conversion to primitive type

The process logically should go both ways, and so there's this:

publicstaticimplicitoperatorT(Null<T> obj){if(obj.hasValue){return obj.value; }elsethrownewException("Value is null."); }

If the value is present, it is returned. If the value is not present, an exception is thrown, as it would not be possible to convert "null" to the chosen primitive type (that's why you would make a struct like this, after all). Other option would be to return instead of throwing an exception, but to me that looks like a decision that you may later regret.

Implicit conversion from null

Now, this is where the things get interesting.

publicstaticimplicitoperatorNull<T>(NoValue noValue){returnnewNull<T>(false, default(T)); }publicclass NoValue {}

Depending on your familiarity with these things, from a glance at the above code you may already have a couple of questions. So let me explain a little:

  • "null" itself is not a "type" in C#, thus cannot be used directly.
  • At the same time, null "belongs to" any "reference" type as the default value.
  • So we declare a special "no value" type that a null-value can be safely cast to.
  • When an implicit cast of null to the struct is required, having no other logical options, the compiler assumes that "null" is a NoValue, calling the conversion function.

So, there - it's weird but it does work just fine. And a small test being

Null<int> i = 3; Console.WriteLine("i:" + i); i = null; Console.WriteLine("n:" + i);

will output

i:3 n:null

just like you would expect it to. Add a couple extra operators, and you'll have a nice system that both works optimally and is convenient to use.

Have fun!

Related posts:

Haxe | csharp, haxe |

Thanks for reading!
You can also find me on Twitter or Tumblr if you'd like.