Een structure in C# is een samengesteld datatype dat bestaat uit een aantal andere elementen van een ander type. Een C# structure is een value type en een instantie (of object) van een structure wordt gecreëerd in de stack. De structure kan velden, methoden, constanten, constructors, properties, indexes, operators en zelfs andere structures bevatten.
De meest gehanteerde toepassing van een struct is bij het gebruikt van lichtgewicht objecten zoals een Point, een Rectangle en Color. Het is natuurlijk ook mogelijk om een point in een class te zetten, maar vaak ik een struct efficiënter. Als je bijvoorbeeld een array van 1000 Point objecten declareert, heb je extra geheugen nodig voor de refenties naar ieder object. In zo'n geval is een struct een betere optie. Een struct gebruikt namelijk alleen geheugen op de stack en niet op de heap (zie http://whatis.techtarget.com/definition/0,,sid9_gci212239,00.html voor het verschil tussen de stack en de heap).
Het .NET Framework kent eigen value types, zoals System.Int32 en System.Boolean, die overeenkomen en in feite identiek zijn aan de primitieve datatypen zoals deze in programmeertalen gebruikt worden.
Alle ingebouwde datatypen in .NET zijn structures. In .NET zijn alle value types en reference types afgeleid van System.Object. De System namespace is de root namespace voor alle typen in het .NET Framework. Deze namespace bevat classes die de basis vertegenwoordigen voor de datatypen die door alle applicaties gebruikt worden. Voorbeelden zijn: Ojbect (het eerste type in de hiërarchie), Byte, Char, Array, Int32, String, enzovoort. Vaak komen deze typen overeen met de primitieve datatypen die in programmeertalen gebruikt worden.
Alle simpele typen zijn aliasen voor .NET Framework System typen. Bijvoorbeeld: int is een alias voor System.Int32. De C# type sleutelwoorden (keywords) en hun alias kunnen elkaar vervangen. Je kunt dus een integer variabele declaren door beide declaraties zoals we ze hieronder zien:
int x = 123;
// hier declareren we een variabele met een structure van het type System.Int32
System.Int32 x = 123;
Wanneer je dus een integer declareert via int x = 9 of via System.Int32 x=9 bereik je exact hetzelfde. Hieronder zie je een overzicht van simpele datatypen en de structure van hetzelfde datatype:
Hieronder zie je hoe een Int32 in C# code er uit ziet:
[Serializable] public struct Int32: IComparable, IFormattable { public const int MinValue = -2147483648; public const int MaxValue = 2147483647; public static string ToString(int i) { // Insert code here. } public string ToString(string format, IFormatProvider formatProvider) { // Insert code here. } public override string ToString() { // Insert code here. } public static int Parse(string s) { // Insert code here. return 0; } public override int GetHashCode() { // Insert code here. return 0; } public override bool Equals(object obj) { // Insert code here. return false; } public int CompareTo(object obj) { // Insert code here. return 0; } }
Lokale variabelen moeten in C# eerst geïnitialiseerd worden voordat ze kunnen worden gebruikt. Wanneer je dus met int mijnInt; een variable declareert, dan kan je hem nog niet gebruiken.
Je kunt de initalisering als volgt doen:
myInt = new int(); // Roep de defaultconstructor aan voor het type int // wat hetzelfde is als myInt = 0; // geen een initële waarde, 0 in dit voorbeeld.
De new operator zorgt voor het aanroepen van de default constructor van het betreffende type en vult de variabele met een default waarde. Zo zorgt de constructor van een int datatype voor een default waarde van 0 (nul).
Hoe maak je nu je eigen struct?
Je kunt het keyword struct gebruiken om een struct te declareren (simpel hè?). De algemene vorm van een struct gaat als volgt.
<modifiers> struct <struct_name> { //Structure members } public struct Point { public int x, y; public Point(int p1, int p2) { x = p1; y = p2; } }
the <modifier> kan zijn: public, protected, internal en private. Je kunt overigens ook nog attributen gebruiken. Deze komen voor de <modifier>.
Een modifier heeft dezelfde betekenis als bij een class declaratie. Je kunt dus niet meerdere modifiers op een struct declaratie toepassen. Het is niet mogelijk om abstracte of gesloten (sealed) modifiers te gebruiken. In de meeste gevallen zul je een public struct creëren.
In een class is het mogelijk om een veld/variabele te creëren en deze direct een waarde toe te kennen. In een structure is een dergelijke initialisatie niet mogelijk. De velden moeten geïnitialiseerd worden via functies, of door het object zelf te gebruiken. De volgende code levert dus een fout op:
struct Point { public int x = 20; // Fout: het is niet mogelijk een veld te initialiseren
public int y=20; // Fout: het is niet mogelijk een veld te initialiseren
}
// Maar een struct kan wel static fields bevatten, die in een struct geïnitialiseerd kan worden.
struct Point {
public static int x = 25;
public static int y = 50;
}
Een C# struct kan ook methoden bevatten. Deze methoden kunnen zowel statisch als niet-statisch zijn. Het is dan wel zo dat statische methoden alleen statische (struct-)members kan benaderen en je kunt ze niet aanroepen via een instantie van de structure. Je kunt ze alleen via de struct-naam benaderen.
struct Point { static int x = 25; static int y = 50; public void SetXY(int i, int j) { x = i; y = j; } public static void ShowSum() { int sum = x + y; Console.WriteLine("De som is {0}",sum); }
In C# heeft iedere value type impliciet een publieke, parameterloze, default constructor. Net als iedere andere constructor wordt de default constructor aangeroepen via new. Het is niet mogelijk dat een struct type een expliciete declaratie heeft van een parameter-loze constructor. Je kunt in een struct type wel constuctors maken mét een of meerdere parameters.
Een C# struct kan een constructor hebben, maar dat mag alleen als deze ook parameters heeft. Zo'n geparametriseerde constructor kan bovendien overloaded worden.
Het is ook mogelijk dat een instantie van een struct wordt gedefinieerd, zonder gebruik te maken van de new operator. Het probleem is dan, dat alle velden in de struct niet geïnitialiseerd zijn, en dus niet gebruikt kunnen worden zolang de velden in de struct geen waarden hebben.
Er is geen overerving voor structs, zoals we die bij classes kennen. Een struct kan niet van een andere struct erven of van een class. Je kunt dus ook geen struct gebruiken als de basis van een class. Een struct erft echter wel van de base class Object. Een struct kan interfaces implementeren, net als dat bij classes kan. Hier zie een stukje code waarin een struct een interface implementeert:
struct Point { int x ; int y ; public Point(int i, int j) { x=i; y=j; } public Point(int i) { x = y = i; } public void ShowXY() { Console.WriteLine("De veldwaarden zijn {0} & {1}",x,y); } } }
Net als classes kan een C# struct een implementatie van een interface op zich nemen. Bijvoorbeeld:
interface MyInterface { void MyMethod(); } struct MyStruct : MyInterface { public void MyMethod() { Console.WriteLine("Struct Methode van MyStruct"); Console.ReadLine(); } } // end of structure class Class1 { /// /// The main entry point for the application. /// [STAThread] static void Main(string[] args) { MyInterface obj= new MyStruct(); obj.MyMethod(); // dit levert een fout op } } // end of main class
Wanneer een structure een interface implementeert, moet je wel een structure object instantiëren via de new operator.
In de onderstaande code gebruiken we geen new, en het werkt prima.
public struct MyStruct1 { public int x; public int y; public void MyMethod() { Console.WriteLine("Struct Methode van MyStruct1"); Console.ReadLine(); } } // end of structure class Class1 { /// /// The main entry point for the application. /// [STAThread] static void Main(string[] args) { MyStruct1 obj; obj.x=9; obj.y=99; obj.MyMethod(); } } // end of main class
Maar als we de structure willen gebruiken, en hij is nog niet geïnitialiseerd, dan treedt er een fout op. De volgende code illustreert dit
public struct MyStruct1 { public int x; public int y; public void MyMethod() { Console.WriteLine("Struct Methode van MyStruct1"); Console.ReadLine(); } } // end of structure class Class1 { /// /// The main entry point for the application. /// [STAThread] static void Main(string[] args) { MyStruct1 obj; obj.MyMethod(); // dit levert een fout op } } // end of main class
We kunnen ook geneste structs declareren. Dat wil zeggen dat je een struct in een struct opneemt. Kijk maar eens naar de volgende code:
public struct Outter { public int x ; public struct Inner { public int y; } }// end of structure class Class1 { /// /// The main entry point for the application. /// [STAThread] static void Main(string[] args) { Outter O ; O.x = 10 ; Outter.Inner I ; I.y = 20 ; Console.WriteLine ( "{0} {1} ",O.x,I.y ) ; Console.ReadLine(); } } // end of main class
Structs lijken in veel opzichten op classes, maar er zijn enkele belangrijke verschillen. Ten eerste, classes zijn reference types en structs niet, dat zijn value types. Door gebruik te maken van structs kan je objecten creëren die hetzelfde werken als de ingebouwde value typen (Int32, Boolean, enz.).
Als je de new operator gebruikt om een instantie van een class te creëren, dan wordt deze instantie gealloceerd op de heap. Als je daarentegen een struct instantieert, komt het object op de stack terecht. Dit kan behoorlijke performance winsten opleveren. Je werkt bovendien niet met een referentie naar het object wanneer je structs gebruikt, maar rechtstreeks met de instantie van een struct. Als je een struct-object doorgeeft aan een methode, dan worden de waarden van het object doorgegeven, en geen referentie naar het object.
Het is niet mogelijk om een destructor te maken of aan te roepen, want structures worden, zoals gezegd, in het stack geheugen geladen.
Structs zijn simpel te gebruiken en kunnen vaak handig zijn. Hou er rekening mee dat structs op de stack worden gezet en dat je het dus niet via een referentie benadert maar rechtstreeks. Vaak gebruik je een struct als je een datatype nodig hebt en die vaak niet meer zijn dan een stukje data.