En rundtur i C#-språket

C# (uttalas "See Sharp") är ett modernt, objektorienterat och typsäkert programmeringsspråk. Med C# kan utvecklare skapa många typer av säkra och robusta program som körs i .NET. C# har sina rötter i C-språkfamiljen och kommer omedelbart att känna till C-, C++-, Java- och JavaScript-programmerare. Den här rundturen ger en översikt över de viktigaste komponenterna i språket i C# 8 och tidigare. Om du vill utforska språket med hjälp av interaktiva exempel kan du prova introduktionen till C# -självstudier.

C# är ett objektorienterat, komponentorienterat programmeringsspråk. C# tillhandahåller språkkonstruktioner som direkt stöder dessa begrepp, vilket gör C# till ett naturligt språk där du kan skapa och använda programvarukomponenter. Sedan dess ursprung har C# lagt till funktioner för att stödja nya arbetsbelastningar och nya metoder för programvarudesign. I grunden är C# ett objektorienterat språk. Du definierar typer och deras beteende.

Flera C#-funktioner hjälper dig att skapa robusta och hållbara program. Skräpinsamling återtar automatiskt minne som används av oanvända objekt som inte kan nås. Nullbara typer skyddar mot variabler som inte refererar till allokerade objekt. Undantagshantering ger en strukturerad och utökningsbar metod för felidentifiering och återställning. Lambda-uttryck stöder funktionella programmeringstekniker . LINQ-syntaxen (Language Integrated Query) skapar ett vanligt mönster för att arbeta med data från alla källor. Språkstöd för asynkrona åtgärder ger syntax för att skapa distribuerade system. C# har ett enhetligt typsystem. Alla C#-typer, inklusive primitiva typer som int och double, ärver från en enda rottyp object . Alla typer delar en uppsättning vanliga åtgärder. Värden av alla typer kan lagras, transporteras och användas på ett konsekvent sätt. Dessutom stöder C# både användardefinierade referenstyper och värdetyper. C# möjliggör dynamisk allokering av objekt och inbyggd lagring av lätta strukturer. C# stöder allmänna metoder och typer, vilket ger ökad typsäkerhet och prestanda. C# tillhandahåller iteratorer som gör det möjligt för implementerare av samlingsklasser att definiera anpassade beteenden för klientkod.

C# betonar versionshantering för att säkerställa att program och bibliotek kan utvecklas över tid på ett kompatibelt sätt. Aspekter av C#:s design som påverkades direkt av versionsöverväganden omfattar separata virtual och override modifierare, regler för metodöverbelastningsmatchning och stöd för explicita medlemsdeklarationer för gränssnitt.

.NET-arkitektur

C#-program körs på .NET, ett virtuellt körningssystem som kallas common language runtime (CLR) och en uppsättning klassbibliotek. CLR är microsofts implementering av den gemensamma språkinfrastrukturen (CLI), en internationell standard. CLI är grunden för att skapa körnings- och utvecklingsmiljöer där språk och bibliotek fungerar sömlöst tillsammans.

Källkoden som skrivits i C# kompileras till ett mellanliggande språk (IL) som överensstämmer med CLI-specifikationen. IL-koden och resurserna, till exempel bitmappar och strängar, lagras i en sammansättning, vanligtvis med ett tillägg av.dll. En sammansättning innehåller ett manifest som innehåller information om sammansättningens typer, version och kultur.

När C#-programmet körs läses sammansättningen in i CLR. CLR utför JIT-kompilering (Just-In-Time) för att konvertera IL-koden till interna datorinstruktioner. CLR tillhandahåller andra tjänster som rör automatisk skräpinsamling, undantagshantering och resurshantering. Kod som körs av CLR kallas ibland "hanterad kod". "Ohanterad kod" kompileras till ett internt datorspråk som riktar sig mot en specifik plattform.

Språksamverkans är en viktig funktion i .NET. IL-kod som produceras av C#-kompilatorn överensstämmer med CTS (Common Type Specification). IL-kod som genereras från C# kan interagera med kod som genererades från .NET-versionerna av F#, Visual Basic, C++. Det finns fler än 20 andra CTS-kompatibla språk. En enskild sammansättning kan innehålla flera moduler skrivna på olika .NET-språk. Typerna kan referera till varandra som om de hade skrivits på samma språk.

Förutom körningstjänster innehåller .NET även omfattande bibliotek. De här biblioteken stöder många olika arbetsbelastningar. De är ordnade i namnområden som ger en mängd användbara funktioner. Biblioteken omfattar allt från filindata och utdata till strängmanipulering till XML-parsning, till webbprogramramverk till Windows Forms kontroller. Det typiska C#-programmet använder .NET-klassbiblioteket i stor utsträckning för att hantera vanliga "VVS"-sysslor.

Mer information om .NET finns i Översikt över .NET.

Hello World

Programmet "Hello, World" används traditionellt för att introducera ett programmeringsspråk. Här är den i C#:

using System;

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

Programmet "Hello, World" börjar med ett using direktiv som refererar till System namnområdet. Namnområden är ett hierarkiskt sätt att organisera C#-program och -bibliotek. Namnområden innehåller typer och andra namnområden, System till exempel innehåller namnområdet ett antal typer, till exempel klassen Console som refereras i programmet och ett antal andra namnområden, till exempel IO och Collections. Ett using direktiv som refererar till ett givet namnområde möjliggör okvalificerad användning av de typer som är medlemmar i det namnområdet. På grund av using direktivet kan programmet använda Console.WriteLine som förkortning för System.Console.WriteLine.

Klassen Hello som deklareras av programmet "Hello, World" har en enda medlem, metoden med namnet Main. Metoden Main deklareras med static modifieraren. Instansmetoder kan referera till en viss omslutande objektinstans med nyckelordet this, men statiska metoder fungerar utan referens till ett visst objekt. Enligt konventionen fungerar en statisk metod med namnet Main som startpunkt för ett C#-program.

Utdata från programmet skapas av WriteLine -metoden för Console klassen i System namnområdet. Den här klassen tillhandahålls av standardklassbiblioteken, som som standard automatiskt refereras av kompilatorn.

Typer och variabler

En typ definierar strukturen och beteendet för alla data i C#. Deklarationen av en typ kan innehålla dess medlemmar, bastyp, gränssnitt som den implementerar och åtgärder som tillåts för den typen. En variabel är en etikett som refererar till en instans av en viss typ.

Det finns två typer av typer i C#: värdetyper och referenstyper. Variabler för värdetyper innehåller direkt sina data. Variabler för referenstyper lagrar referenser till sina data, där de senare kallas objekt. Med referenstyper är det möjligt för två variabler att referera till samma objekt och möjligt för åtgärder på en variabel att påverka objektet som refereras av den andra variabeln. Med värdetyper har variablerna var sin egen kopia av data och det är inte möjligt för åtgärder på den ena att påverka den andra (förutom refout parametervariabler).

En identifierare är ett variabelnamn. En identifierare är en sekvens med unicode-tecken utan blanksteg. En identifierare kan vara ett reserverat C#-ord om det är prefixet .@ Att använda ett reserverat ord som identifierare kan vara användbart när du interagerar med andra språk.

C#:s värdetyper är ytterligare indelade i enkla typer, uppräkningstyper, structtyper, nullbara värdetyper och tuppelns värdetyper. C#:s referenstyper är ytterligare indelade i klasstyper, gränssnittstyper, matristyper och ombudstyper.

Följande disposition ger en översikt över C#:s typsystem.

C#-program använder typdeklarationer för att skapa nya typer. En typdeklaration anger namnet och medlemmarna av den nya typen. Sex av C#:s kategorier av typer är användardefinierbara: klasstyper, structtyper, gränssnittstyper, uppräkningstyper, ombudstyper och tuppelns värdetyper. Du kan också deklarera record typer, antingen record struct, eller record class. Posttyper har kompilatorsyntiserade medlemmar. Du använder poster främst för att lagra värden, med minimalt associerat beteende.

  • En class typ definierar en datastruktur som innehåller datamedlemmar (fält) och funktionsmedlemmar (metoder, egenskaper och andra). Klasstyper stöder enkel arvs- och polymorfism, mekanismer där härledda klasser kan utöka och specialisera basklasser.
  • En struct typ liknar en klasstyp eftersom den representerar en struktur med datamedlemmar och funktionsmedlemmar. Men till skillnad från klasser är structs värdetyper och kräver vanligtvis inte heap-allokering. Struct-typer stöder inte användardefinierad arv och alla struct-typer ärver implicit från typen object.
  • En interface typ definierar ett kontrakt som en namngiven uppsättning offentliga medlemmar. En class eller struct som implementerar ett interface måste tillhandahålla implementeringar av gränssnittets medlemmar. En interface kan ärva från flera basgränssnitt och en class eller struct kan implementera flera gränssnitt.
  • En delegate typ representerar referenser till metoder med en viss parameterlista och returtyp. Ombud gör det möjligt att behandla metoder som entiteter som kan tilldelas till variabler och skickas som parametrar. Ombuden motsvarar funktionstyper som tillhandahålls av funktionella språk. De liknar också begreppet funktionspekare som finns på vissa andra språk. Till skillnad från funktionspekare är ombud objektorienterade och typsäkra.

Typerna class, struct, interfaceoch delegate stöder alla generiska objekt, där de kan parametriseras med andra typer.

C# stöder endimensionella och flerdimensionella matriser av alla typer. Till skillnad från de typer som anges ovan behöver matristyper inte deklareras innan de kan användas. I stället skapas matristyper genom att följa ett typnamn med hakparenteser. Till exempel int[] är en endimensionell matris med int, int[,] är en tvådimensionell matris med int, och int[][] är en endimensionell matris med endimensionella matriser, eller en "ojämn" matris, av int.

Nullbara typer kräver ingen separat definition. För varje icke-nullbar typ Tfinns det en motsvarande nullbar typ T?, som kan innehålla ytterligare ett värde, null. Är till exempel int? en typ som kan innehålla valfritt 32-bitars heltal eller värde nulloch string? är en typ som kan innehålla valfritt string eller värdet null.

C#:s typsystem är enhetligt så att ett värde av alla typer kan behandlas som en object. Varje typ i C# härleds direkt eller indirekt från object klasstypen och object är den ultimata basklassen av alla typer. Värden för referenstyper behandlas som objekt genom att bara visa värdena som typ object. Värden för värdetyper behandlas som objekt genom att utföra boxnings- och avboxningsåtgärder. I följande exempel konverteras ett int värde till object och tillbaka igen till int.

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

När ett värde av en värdetyp tilldelas till en object referens allokeras en "ruta" för att lagra värdet. Den rutan är en instans av en referenstyp och värdet kopieras till den rutan. När en object referens konverteras till en värdetyp markeras däremot att den refererade object är en ruta med rätt värdetyp. Om kontrollen lyckas kopieras värdet i rutan till värdetypen.

C#:s enhetliga typsystem innebär effektivt att värdetyper behandlas som object referenser "på begäran". På grund av enandet kan allmänna bibliotek som använder typen object användas med alla typer som härleds från object, inklusive både referenstyper och värdetyper.

Det finns flera typer av variabler i C#, inklusive fält, matriselement, lokala variabler och parametrar. Variabler representerar lagringsplatser. Varje variabel har en typ som avgör vilka värden som kan lagras i variabeln, enligt nedan.

  • Värdetyp som inte kan null
    • Ett värde av den exakta typen
  • Värdetyp som kan vara null
    • Ett null värde eller ett värde av den exakta typen
  • objekt
    • En null referens, en referens till ett objekt av valfri referenstyp eller en referens till ett boxat värde av valfri värdetyp
  • Klasstyp
    • En null referens, en referens till en instans av den klasstypen eller en referens till en instans av en klass som härletts från den klasstypen
  • Gränssnittstyp
    • En null referens, en referens till en instans av en klasstyp som implementerar den gränssnittstypen eller en referens till ett boxat värde av en värdetyp som implementerar den gränssnittstypen
  • Matristyp
    • En null referens, en referens till en instans av den matristypen eller en referens till en instans av en kompatibel matristyp
  • Ombudstyp
    • En null referens eller en referens till en instans av en kompatibel ombudstyp

Programstruktur

De viktigaste organisationsbegreppen i C# är program, namnrymder, typer, medlemmar och sammansättningar. Program deklarerar typer som innehåller medlemmar och kan ordnas i namnområden. Klasser, structs och gränssnitt är exempel på typer. Fält, metoder, egenskaper och händelser är exempel på medlemmar. När C#-program kompileras paketeras de fysiskt i sammansättningar. Sammansättningar har vanligtvis filnamnstillägget .exe eller .dll, beroende på om de implementerarprogram eller bibliotek.

Som ett litet exempel bör du överväga en sammansättning som innehåller följande kod:

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;
        }
    }
}

Det fullständigt kvalificerade namnet på den här klassen är Acme.Collections.Stack. Klassen innehåller flera medlemmar: ett fält med namnet _top, två metoder med namnet Push och Pop, och en kapslad klass med namnet Entry. Klassen Entry innehåller ytterligare tre medlemmar: en egenskap med namnet Next, en egenskap med namnet Dataoch en konstruktor. Stack är en allmän klass. Den har en typparameter som T ersätts med en betongtyp när den används.

En stack är en FILO-samling (first in - last out). Nya element läggs till överst i stacken. När ett element tas bort tas det bort från stackens överkant. I föregående exempel deklareras den Stack typ som definierar lagring och beteende för en stack. Du kan deklarera en variabel som refererar till en instans av typen för att använda den Stack funktionen.

Sammansättningar innehåller körbar kod i form av instruktioner för mellanliggande språk (IL) och symbolisk information i form av metadata. Innan den körs konverterar JIT-kompilatorn (Just-In-Time) för .NET Common Language Runtime IL-koden i en sammansättning till processorspecifik kod.

Eftersom en sammansättning är en självbeskrivande funktionsenhet som innehåller både kod och metadata behöver #include du inte ha direktiv och huvudfiler i C#. De offentliga typerna och medlemmarna som ingår i en viss sammansättning görs tillgängliga i ett C#-program genom att referera till sammansättningen när programmet kompileras. Det här programmet använder Acme.Collections.Stack till exempel klassen från acme.dll sammansättningen:

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
    }
}

För att kompilera det här programmet måste du referera till sammansättningen som innehåller stackklassen som definierades i det tidigare exemplet.

C#-program kan lagras i flera källfiler. När ett C#-program kompileras bearbetas alla källfiler tillsammans och källfilerna kan fritt referera till varandra. Konceptuellt är det som om alla källfiler sammanfogades till en stor fil innan de bearbetades. Framåtdeklarationer behövs aldrig i C# eftersom deklarationsordningen med få undantag är obetydlig. C# begränsar inte en källfil till att endast deklarera en offentlig typ och kräver inte heller namnet på källfilen för att matcha en typ som deklarerats i källfilen.

Ytterligare artiklar i den här rundturen förklarar dessa organisationsblock.