Een rondleiding door de C#-taal

C# (uitgesproken als "See Sharp") is een moderne, objectgeoriënteerde en typeveilige programmeertaal. C# stelt ontwikkelaars in staat om veel soorten veilige en robuuste toepassingen te bouwen die worden uitgevoerd in .NET. C# heeft zijn wortels in de C-familie van talen en zal onmiddellijk bekend zijn met C-, C++-, Java- en JavaScript-programmeurs. Deze rondleiding biedt een overzicht van de belangrijkste onderdelen van de taal in C# 8 en eerder. Als u de taal wilt verkennen via interactieve voorbeelden, kunt u de inleiding tot C# -zelfstudies proberen.

C# is een objectgeoriënteerde, onderdeelgeoriënteerde programmeertaal. C# biedt taalconstructies om deze concepten rechtstreeks te ondersteunen, waardoor C# een natuurlijke taal wordt waarin softwareonderdelen moeten worden gemaakt en gebruikt. Sinds de oorsprong heeft C# functies toegevoegd ter ondersteuning van nieuwe workloads en opkomende softwareontwerpprocedures. C# is een objectgeoriënteerde taal. U definieert typen en hun gedrag.

Verschillende C#-functies helpen bij het maken van robuuste en duurzame toepassingen. Garbagecollection maakt automatisch geheugen vrij door onbereikbare ongebruikte objecten. Null-typen zijn beveiligd tegen variabelen die niet verwijzen naar toegewezen objecten. Uitzonderingsafhandeling biedt een gestructureerde en uitbreidbare benadering van foutdetectie en herstel. Lambda-expressies ondersteunen functionele programmeertechnieken. Linq-syntaxis (Language Integrated Query) maakt een gemeenschappelijk patroon voor het werken met gegevens uit elke bron. Taalondersteuning voor asynchrone bewerkingen biedt syntaxis voor het bouwen van gedistribueerde systemen. C# heeft een geïntegreerd typesysteem. Alle C#-typen, inclusief primitieve typen, zoals int en double, nemen over van één hoofdtype object . Alle typen delen een reeks algemene bewerkingen. Waarden van elk type kunnen op een consistente manier worden opgeslagen, vervoerd en uitgevoerd. Bovendien ondersteunt C# zowel door de gebruiker gedefinieerde referentietypen als waardetypen. C# maakt dynamische toewijzing van objecten en inline opslag van lichtgewicht structuren mogelijk. C# ondersteunt algemene methoden en typen, die meer veiligheid en prestaties van het type bieden. C# biedt iterators, waarmee implementeerders van verzamelingsklassen aangepast gedrag voor clientcode kunnen definiëren.

C# benadrukt versiebeheer om ervoor te zorgen dat programma's en bibliotheken zich op een compatibele manier kunnen ontwikkelen. Aspecten van het ontwerp van C# die rechtstreeks zijn beïnvloed door versiebeheeroverwegingen, zijn de afzonderlijke virtual en override modifiers, de regels voor het oplossen van overbelasting van methoden en ondersteuning voor expliciete declaraties van interfaceleden.

.NET-architectuur

C#-programma's worden uitgevoerd op .NET, een virtueel uitvoeringssysteem met de naam Common Language Runtime (CLR) en een set klassebibliotheken. De CLR is de implementatie door Microsoft van de algemene taalinfrastructuur (CLI), een internationale standaard. De CLI is de basis voor het maken van uitvoerings- en ontwikkelomgevingen waarin talen en bibliotheken naadloos samenwerken.

Broncode die is geschreven in C# wordt gecompileerd in een tussenliggende taal (IL) die voldoet aan de CLI-specificatie. De IL-code en -resources, zoals bitmaps en tekenreeksen, worden opgeslagen in een assembly, meestal met een uitbreiding van .dll. Een assembly bevat een manifest dat informatie biedt over de typen, versies en cultuur van de assembly.

Wanneer het C#-programma wordt uitgevoerd, wordt de assembly in de CLR geladen. De CLR voert JIT-compilatie (Just-In-Time) uit om de IL-code te converteren naar systeemeigen machine-instructies. De CLR biedt andere services met betrekking tot automatische garbagecollection, uitzonderingsafhandeling en resourcebeheer. Code die wordt uitgevoerd door de CLR wordt soms 'beheerde code' genoemd. 'Niet-beheerde code' wordt gecompileerd in de systeemeigen computertaal die gericht is op een specifiek platform.

Taalinteroperabiliteit is een belangrijke functie van .NET. IL-code die door de C#-compiler wordt geproduceerd, voldoet aan de Common Type Specification (CTS). IL-code die is gegenereerd op basis van C# kan communiceren met code die is gegenereerd op basis van de .NET-versies van F#, Visual Basic, C++. Er zijn meer dan 20 andere CTS-compatibele talen. Eén assembly kan meerdere modules bevatten die zijn geschreven in verschillende .NET-talen. De typen kunnen naar elkaar verwijzen alsof ze in dezelfde taal zijn geschreven.

Naast de runtimeservices bevat .NET ook uitgebreide bibliotheken. Deze bibliotheken ondersteunen veel verschillende workloads. Ze zijn ingedeeld in naamruimten die een grote verscheidenheid aan nuttige functionaliteit bieden. De bibliotheken bevatten alles, van bestandsinvoer en uitvoer tot tekenreeksbewerking tot XML-parsering, tot webtoepassingsframeworks tot Windows Forms besturingselementen. De typische C#-toepassing gebruikt de .NET-klassebibliotheek uitgebreid voor het afhandelen van algemene 'loodgieters'-taken.

Zie Overzicht van .NET voor meer informatie over .NET.

Hallo wereld

Het programma 'Hello, World' wordt traditioneel gebruikt om een programmeertaal te introduceren. Hier bevindt het zich in C#:

using System;

class Hello
{
    static void Main()
    {
        Console.WriteLine("Hello, World");
    }
}

Het programma 'Hallo, wereld' begint met een using instructie die verwijst naar de System naamruimte. Naamruimten bieden een hiërarchische methode voor het organiseren van C#-programma's en bibliotheken. Naamruimten bevatten typen en andere naamruimten, bijvoorbeeld de System naamruimte bevat een aantal typen, zoals de Console klasse waarnaar in het programma wordt verwezen, en een aantal andere naamruimten, zoals IO en Collections. Een using instructie die verwijst naar een bepaalde naamruimte maakt niet-gekwalificeerd gebruik mogelijk van de typen die lid zijn van die naamruimte. Vanwege de using richtlijn kan het programma als afkorting worden gebruiktConsole.WriteLine.System.Console.WriteLine

De Hello klasse die is gedeclareerd door het programma 'Hallo, wereld' heeft één lid, de methode met de naam Main. De Main methode wordt gedeclareerd met de static wijzigingsfunctie. Hoewel exemplaarmethoden kunnen verwijzen naar een bepaald objectexemplaren met behulp van het trefwoord this, werken statische methoden zonder verwijzing naar een bepaald object. Volgens conventie fungeert een statische methode die wordt genoemd Main als het toegangspunt van een C#-programma.

De uitvoer van het programma wordt geproduceerd door de WriteLine methode van de Console klasse in de System naamruimte. Deze klasse wordt geleverd door de standaardklassebibliotheken, die standaard automatisch worden verwezen door de compiler.

Typen en variabelen

Een type definieert de structuur en het gedrag van gegevens in C#. De declaratie van een type kan bestaan uit leden, basistype, interfaces die worden geïmplementeerd en bewerkingen die zijn toegestaan voor dat type. Een variabele is een label dat verwijst naar een exemplaar van een specifiek type.

Er zijn twee soorten typen in C#: waardetypen en verwijzingstypen. Variabelen van waardetypen bevatten hun gegevens rechtstreeks. Variabelen van verwijzingstypen slaan verwijzingen naar hun gegevens op, die laatste objecten worden genoemd. Met verwijzingstypen is het mogelijk dat twee variabelen verwijzen naar hetzelfde object en mogelijk zijn voor bewerkingen op één variabele om het object te beïnvloeden waarnaar wordt verwezen door de andere variabele. Met waardetypen hebben de variabelen elk hun eigen kopie van de gegevens en is het niet mogelijk om bewerkingen op de ene te beïnvloeden (met uitzondering van ref en out parametervariabelen).

Een id is een variabelenaam. Een id is een reeks Unicode-tekens zonder witruimte. Een id kan een gereserveerd C#-woord zijn, als deze wordt voorafgegaan door @. Het gebruik van een gereserveerd woord als id kan handig zijn bij interactie met andere talen.

De waardetypen van C# zijn verder onderverdeeld in eenvoudige typen, opsommingstypen, structtypen, null-waardetypen en tuple-waardetypen. De referentietypen van C# zijn verder onderverdeeld in klassetypen, interfacetypen, matrixtypen en gedelegeerdentypen.

Het volgende overzicht bevat een overzicht van het typesysteem van C#.

C#-programma's gebruiken typedeclaraties om nieuwe typen te maken. Een typedeclaratie geeft de naam en de leden van het nieuwe type op. Zes categorieën van C#-typen zijn door de gebruiker te definiëren: klassetypen, structtypen, interfacetypen, enumtypen, gedelegeerdentypen en tuple-waardetypen. U kunt ook typen declareren record , ofwel record struct, of record class. Recordtypen hebben compiler-gesynthetiseerde leden. U gebruikt records voornamelijk voor het opslaan van waarden, met minimaal gekoppeld gedrag.

  • Een class type definieert een gegevensstructuur die gegevensleden (velden) en functieleden (methoden, eigenschappen en andere) bevat. Klassetypen ondersteunen enkelvoudige overname en polymorfisme, waarbij afgeleide klassen basisklassen kunnen uitbreiden en specialiseren.
  • Een struct type is vergelijkbaar met een klassetype omdat het een structuur vertegenwoordigt met gegevensleden en functieleden. In tegenstelling tot klassen zijn structs echter waardetypen en vereisen ze doorgaans geen heaptoewijzing. Struct-typen bieden geen ondersteuning voor door de gebruiker opgegeven overname en alle structtypen nemen impliciet over van het type object.
  • Een interface type definieert een contract als een benoemde set openbare leden. Een class of struct die een interface implementatie implementeert, moet implementaties van de leden van de interface bieden. Een interface kan overnemen van meerdere basisinterfaces en een class of struct meerdere interfaces implementeren.
  • Een delegate type vertegenwoordigt verwijzingen naar methoden met een bepaalde parameterlijst en retourtype. Gemachtigden maken het mogelijk om methoden te behandelen als entiteiten die aan variabelen kunnen worden toegewezen en als parameters kunnen worden doorgegeven. Gemachtigden zijn vergelijkbaar met functietypen die worden geleverd door functionele talen. Ze zijn ook vergelijkbaar met het concept van functie-aanwijzers in sommige andere talen. In tegenstelling tot functiepointers zijn gemachtigden objectgeoriënteerd en typeveilig.

De class, structen interfacedelegate typen ondersteunen allemaal generics, waarbij ze kunnen worden geparameteriseerd met andere typen.

C# ondersteunt eendimensionale en multidimensionale matrices van elk type. In tegenstelling tot de bovenstaande typen hoeven matrixtypen niet te worden gedeclareerd voordat ze kunnen worden gebruikt. In plaats daarvan worden matrixtypen samengesteld door een typenaam met vierkante haken te volgen. Is bijvoorbeeld int[] een eendimensionale matrix van int, int[,] is een tweedimensionale matrix van int, en int[][] is een eendimensionale matrix van eendimensionale matrix van eendimensionale matrices, of een 'onregelmatige' matrix, van int.

Voor null-typen is geen afzonderlijke definitie vereist. Voor elk niet-null-type Tis er een bijbehorend type dat null kan T?bevatten, die een extra waarde kan bevatten. null Is bijvoorbeeld int? een type dat elk 32-bits geheel getal of de waarde nullnullkan bevatten en string? een type is dat een string willekeurige waarde of waarde kan bevatten.

Het typesysteem van C#is geïntegreerd, zodat een waarde van elk type kan worden behandeld als een object. Elk type in C# is direct of indirect afgeleid van het object klassetype en object is de ultieme basisklasse van alle typen. Waarden van verwijzingstypen worden behandeld als objecten door de waarden als type objectte bekijken. Waarden van waardetypen worden behandeld als objecten door boks- en uitboxbewerkingen uit te voeren. In het volgende voorbeeld wordt een int waarde geconverteerd naar object en weer naar int.

int i = 123;
object o = i;    // Boxing
int j = (int)o;  // Unboxing

Wanneer een waarde van een waardetype wordt toegewezen aan een object verwijzing, wordt er een 'box' toegewezen om de waarde vast te houden. Dat vak is een exemplaar van een verwijzingstype en de waarde wordt naar dat vak gekopieerd. Wanneer een object verwijzing wordt omgezet naar een waardetype, wordt daarentegen gecontroleerd of de verwijzing object een vak van het juiste waardetype is. Als de controle slaagt, wordt de waarde in het vak gekopieerd naar het waardetype.

Het geïntegreerde typesysteem van C# betekent effectief dat waardetypen worden behandeld als object verwijzingen 'op aanvraag'. Vanwege de eenwording kunnen bibliotheken voor algemeen gebruik die gebruikmaken van het type object worden gebruikt met alle typen die zijn afgeleid van object, inclusief zowel referentietypen als waardetypen.

Er zijn verschillende soorten variabelen in C#, waaronder velden, matrixelementen, lokale variabelen en parameters. Variabelen vertegenwoordigen opslaglocaties. Elke variabele heeft een type dat bepaalt welke waarden in de variabele kunnen worden opgeslagen, zoals hieronder wordt weergegeven.

  • Niet-nullable waardetype
    • Een waarde van dat exacte type
  • Type null-waarde
    • Een null waarde of een waarde van dat exacte type
  • object
    • Een null verwijzing, een verwijzing naar een object van een verwijzingstype of een verwijzing naar een waardetype in een vak
  • Klassetype
    • Een null verwijzing, een verwijzing naar een exemplaar van dat klassetype of een verwijzing naar een exemplaar van een klasse die is afgeleid van dat klassetype
  • Interfacetype
    • Een null verwijzing, een verwijzing naar een exemplaar van een klassetype dat dat interfacetype implementeert, of een verwijzing naar een vakkenwaarde van een waardetype dat dat interfacetype implementeert
  • Matrixtype
    • Een null verwijzing, een verwijzing naar een exemplaar van dat matrixtype of een verwijzing naar een exemplaar van een compatibel matrixtype
  • Type gemachtigde
    • Een null verwijzing of verwijzing naar een exemplaar van een compatibel gemachtigdentype

Programmastructuur

De belangrijkste organisatieconcepten in C# zijn programma's, naamruimten, typen, leden en assembly's. Programma's declareren typen, die leden bevatten en kunnen worden ingedeeld in naamruimten. Klassen, structs en interfaces zijn voorbeelden van typen. Velden, methoden, eigenschappen en gebeurtenissen zijn voorbeelden van leden. Wanneer C#-programma's worden gecompileerd, worden ze fysiek verpakt in assembly's. Assembly's hebben meestal de bestandsextensie .exe of .dll, afhankelijk van of ze toepassingen of bibliotheken implementeren.

Bekijk als een klein voorbeeld een assembly die de volgende code bevat:

namespace Acme.Collections;

public class Stack<T>
{
    Entry _top;

    public void Push(T data)
    {
        _top = new Entry(_top, data);
    }

    public T Pop()
    {
        if (_top == null)
        {
            throw new InvalidOperationException();
        }
        T result = _top.Data;
        _top = _top.Next;

        return result;
    }

    class Entry
    {
        public Entry Next { get; set; }
        public T Data { get; set; }

        public Entry(Entry next, T data)
        {
            Next = next;
            Data = data;
        }
    }
}

De volledig gekwalificeerde naam van deze klasse is Acme.Collections.Stack. De klasse bevat verschillende leden: een veld met de naam _top, twee methoden met de naam Push en Pop, en een geneste klasse met de naam Entry. De Entry klasse bevat nog drie leden: een eigenschap met de naam Next, een eigenschap met de naam Dataen een constructor. Het Stack is een algemene klasse. Het heeft één typeparameter, T die wordt vervangen door een betontype wanneer deze wordt gebruikt.

Een stack is een filo-verzameling (first in - last out). Nieuwe elementen worden toegevoegd aan de bovenkant van de stapel. Wanneer een element wordt verwijderd, wordt dit verwijderd uit de bovenkant van de stack. In het vorige voorbeeld wordt het Stack type declareren dat de opslag en het gedrag voor een stack definieert. U kunt een variabele declareren die verwijst naar een exemplaar van het Stack type om die functionaliteit te gebruiken.

Assembly's bevatten uitvoerbare code in de vorm van tussenliggende taalinstructies (IL) en symbolische informatie in de vorm van metagegevens. Voordat deze wordt uitgevoerd, converteert de JIT-compiler (Just-In-Time) van .NET Common Language Runtime de IL-code in een assembly naar processorspecifieke code.

Omdat een assembly een zelfbeschrijfende eenheid van functionaliteit is die zowel code als metagegevens bevat, is er geen noodzaak voor #include instructies en headerbestanden in C#. De openbare typen en leden in een bepaalde assembly worden beschikbaar gesteld in een C#-programma door te verwijzen naar die assembly bij het compileren van het programma. Dit programma gebruikt bijvoorbeeld de Acme.Collections.Stack klasse van de acme.dll assembly:

class Example
{
    public static void Main()
    {
        var s = new Acme.Collections.Stack<int>();
        s.Push(1); // stack contains 1
        s.Push(10); // stack contains 1, 10
        s.Push(100); // stack contains 1, 10, 100
        Console.WriteLine(s.Pop()); // stack contains 1, 10
        Console.WriteLine(s.Pop()); // stack contains 1
        Console.WriteLine(s.Pop()); // stack is empty
    }
}

Als u dit programma wilt compileren, moet u verwijzen naar de assembly die de stackklasse bevat die in het eerdere voorbeeld is gedefinieerd.

C#-programma's kunnen worden opgeslagen in verschillende bronbestanden. Wanneer een C#-programma wordt gecompileerd, worden alle bronbestanden samen verwerkt en kunnen de bronbestanden vrijelijk naar elkaar verwijzen. Conceptueel gezien is het alsof alle bronbestanden zijn samengevoegd in één groot bestand voordat ze worden verwerkt. Forward-declaraties zijn nooit nodig in C# omdat, met weinig uitzonderingen, de declaratievolgorde niet goed is. C# beperkt een bronbestand niet tot het declareren van slechts één openbaar type en vereist ook niet dat de naam van het bronbestand overeenkomt met een type dat is gedeclareerd in het bronbestand.

In meer artikelen in deze rondleiding worden deze organisatieblokken uitgelegd.