Einführung in .NET

.NET ist eine Entwicklungsplattform für allgemeine Zwecke. Sie bietet mehrere wichtige Features wie Unterstützung für verschiedene Programmiersprachen, asynchrone und gleichzeitige Programmiermodelle und native Interoperabilität. Dadurch wird auf unterschiedlichen Plattformen eine Vielzahl von Szenarios ermöglicht.

Dieser Artikel bietet eine Einführung in einige der wichtigsten Features von .NET. Im Thema .NET-Architekturkomponenten finden Sie Informationen zu den Bestandteilen der Architektur und deren Verwendung.

Ausführen der Codebeispiele

Informationen zum Einrichten einer Entwicklungsumgebung zum Ausführen der Codebeispiele finden Sie im Thema Erste Schritte. Kopieren Sie Codebeispiele auf dieser Seite, und fügen Sie sie zum Ausführen in Ihrer Umgebung ein.

Programmiersprachen

.NET unterstützt mehrere Programmiersprachen. Die .NET-Implementierungen implementieren die Common Language Infrastructure (CLI), die unter anderem eine sprachunabhängige Runtime und Spracheninteroperabilität angibt. Dies bedeutet, dass Sie zum Erstellen von Apps und Diensten in .NET eine beliebige .NET-Sprache auswählen können.

Microsoft entwickelt aktiv drei .NET-Sprachen und unterstützt diese: C#, F# und Visual Basic (VB).

  • C# ist einfach, leistungsstark, typsicher und objektorientiert, behält aber gleichzeitig die Ausdruckskraft und Eleganz der C-Sprachen bei. Wer sich mit C und ähnlichen Sprachen auskennt, hat wenig Probleme bei der Verwendung von C#. Weitere Informationen zu C# finden Sie im Leitfaden für C#.

  • F# ist eine plattformübergreifende, funktionsorientierte Programmiersprache, die auch die herkömmliche und imperative Programmierung unterstützt. Weitere Informationen zu F# finden Sie im Leitfaden für F#.

  • Visual Basic ist eine einfach zu erlernende Sprache, mit der Sie eine Vielzahl von Apps erstellen können, die in .NET ausgeführt werden. Von allen .NET-Sprachen gleicht die Syntax von VB der menschlichen Sprache am meisten. Dies erleichtert den Einstieg in die Softwareentwicklung.

Automatische Speicherverwaltung

.NET verwendet Garbage Collection (GC), um eine automatische Speicherverwaltung für Programme bereitzustellen. Der Garbage Collector arbeitet bei der Speicherverwaltung mit dem Prinzip der Verzögerung und gibt dem App-Durchsatz den Vorzug vor dem sofortigen Erfassen von Arbeitsspeicher. Weitere Informationen zur Garbage Collection in .NET finden Sie unter Grundlagen der Garbage Collection (GC).

Die beiden folgenden Zeilen weisen Speicher zu:

var title = ".NET Primer";
var list = new List<string>();

Es gibt kein entsprechendes Schlüsselwort zum Aufheben der Speicherzuweisung, da diese automatisch erfolgt, wenn der Garbage Collector während seiner geplanten Ausführung Arbeitsspeicher freigibt.

Der Garbage Collector ist nur einer der Dienste, die bei der Sicherstellung der Speichersicherheit helfen. Ein Programm ist speichersicher, wenn es nur auf belegten Speicherplatz zugreift. Beispielsweise stellt die Runtime sicher, dass eine App nicht auf belegten Arbeitsspeicher außerhalb des zulässigen Bereichs eines Arrays zugreift.

Im folgenden Beispiel löst die Runtime eine InvalidIndexException-Ausnahme aus, um Speichersicherheit zu erzwingen:

int[] numbers = new int[42];
int number = numbers[42]; // Will throw an exception (indexes are 0-based)

Arbeiten mit nicht verwalteten Ressourcen

Einige Objekte verweisen auf nicht verwaltete Ressourcen. Nicht verwaltete Ressourcen sind Ressourcen, die nicht automatisch von der .NET-Runtime verwaltet werden. Ein Dateihandle ist z.B. eine nicht verwaltete Ressource. Ein <xref:System.IO.FileStream> -Objekt ist ein verwaltetes Objekt, aber es verweist auf ein Dateihandle, das nicht verwaltet ist. Wenn Sie mit <xref:System.IO.FileStream> fertig sind, müssen Sie das Dateihandle freigeben.

In .NET implementieren Objekte, die auf nicht verwaltete Ressourcen verweisen, die <xref:System.IDisposable> -Schnittstelle. Wenn Sie mit dem Objekt fertig sind, können Sie die <xref:System.IDisposable.Dispose>-Methode des Objekts aufrufen, die für die Freigabe nicht verwalteter Ressourcen zuständig ist. .NET-Sprachen stellen wie im folgenden Beispiel gezeigt eine praktische using-Syntax für solche Objekte bereit:

using System.IO;

using (FileStream stream = GetFileStream(context))
{
    // Operations on the stream
}

Sobald der using-Block abgeschlossen ist, ruft die .NET-Runtime automatisch die <xref:System.IDisposable.Dispose>-Methode des stream-Objekts auf, die das Dateihandle freigibt. Die Runtime tut dies ebenfalls, wenn eine Ausnahme das Steuerelement dazu veranlasst, den Block zu verlassen.

Weitere Einzelheiten dazu finden Sie in folgenden Themen:

Typsicherheit

Ein Objekt ist eine Instanz eines bestimmten Typs. Die einzigen Vorgänge, die für ein bestimmtes Objekt zulässig sind, gehören zum Typ des Objekts. Ein Dog-Typ kann Jump- und WagTail-Methoden besitzen, aber keine SumTotal-Methode. Ein Programm ruft nur die Methoden auf, die zu einem bestimmten Typ gehören. Alle anderen Aufrufe führen entweder zu einem Kompilierzeitfehler oder einer Laufzeitausnahme (bei Verwendung von dynamischen Features oder object).

.NET-Sprachen sind objektorientiert und arbeiten mit Hierarchien aus Basisklassen und abgeleiteten Klassen. Die .NET-Runtime lässt nur Objektumwandlungen und -aufrufe zu, die der Objekthierarchie entsprechen. Denken Sie daran, dass jeder in einer .NET-Sprache definierte Typ vom <xref:System.Object>-Basistyp abgeleitet ist.

Dog dog = AnimalShelter.AdoptDog(); // Returns a Dog type.
Pet pet = (Pet)dog; // Dog derives from Pet.
pet.ActCute();
Car car = (Car)dog; // Will throw - no relationship between Car and Dog.
object temp = (object)dog; // Legal - a Dog is an object.

Mithilfe der Typsicherheit lässt sich auch eine Kapselung erzwingen, indem die Genauigkeit der Accessorschlüsselwörter garantiert wird. Accessorschlüsselwörter sind Artefakte, die den Zugriff auf Member eines bestimmten Typs durch anderen Code steuern. Diese werden üblicherweise für verschiedene Arten von Daten innerhalb eines Typs verwendet, mit denen das Verhalten des Typs verwaltet wird.

private Dog _nextDogToBeAdopted = AnimalShelter.AdoptDog()

C#, VB und F# unterstützen einen lokalen Typrückschluss. Ein Typrückschluss bedeutet, dass der Compiler den Typ eines Ausdrucks auf der linken Seite aus dem Ausdruck auf der rechten Seite ableitet. Dies bedeutet nicht, dass die Typsicherheit verletzt oder außer Kraft gesetzt wird. Der resultierende Typ besitzt einen starken Typ mit allem, was dies impliziert. dog und cat aus dem vorherigen Beispiel werden umgeschrieben, um einen Typrückschluss einzufügen. Der Rest des Beispiels bleibt unverändert:

var dog = AnimalShelter.AdoptDog();
var pet = (Pet)dog;
pet.ActCute();
Car car = (Car)dog; // will throw - no relationship between Car and Dog
object temp = (object)dog; // legal - a Dog is an object
car = (Car)temp; // will throw - the runtime isn't fooled
car.Accelerate() // the dog won't like this, nor will the program get this far

F# weist sogar noch weitergehende Funktionen für den Typrückschluss auf als nur den Typrückschluss innerhalb einer Methode wie in C# und VB. Weitere Informationen finden Sie unter Typrückschluss.

Delegaten und Lambdas

Ein Delegat wird durch eine Methodensignatur dargestellt. Jede Methode mit dieser Signatur kann dem Delegaten zugewiesen werden und wird ausgeführt, wenn der Delegat aufgerufen wird.

Delegaten ähneln C++-Funktionszeigern, sind aber typsicher. Sie stellen eine Art separater Methode innerhalb des CLR-Typsystems dar. Reguläre Methoden werden an eine Klasse angefügt und können nur über statische oder instanzaufrufende Konventionen direkt aufgerufen werden.

Delegaten werden in .NET häufig in Ereignishandlern, beim Definieren asynchroner Vorgänge und in Lambdaausdrücken verwendet, die einer der Eckpfeiler von LINQ sind. Weitere Informationen finden Sie im Thema Delegaten und Lambdas.

Generika

Generika ermöglichen dem Programmierer, beim Entwerfen der Klassen einen Typparameter einzuführen, über den der Clientcode (die Benutzer des Typs) den genauen Typ angeben kann, der anstelle des Typparameters verwendet werden soll.

Generika wurden hinzugefügt, um Programmierer beim Implementieren generischer Datenstrukturen zu unterstützen. Vor der Einführung von Generika mussten Programmierer mit Elementen vom Typ object arbeiten, um z.B. den Typ List generisch zu machen. Dies hat zu verschiedenen Leistungs- und Semantikproblemen sowie zu möglichen kleinen Runtimefehlern geführt. Die bekannteste Variante solcher Fehler tritt auf, wenn eine Datenstruktur z.B. sowohl ganze Zahlen als auch Zeichenfolgen enthält und beim Arbeiten mit den Members der Liste eine InvalidCastException ausgelöst wird.

Das folgende Beispiel zeigt ein einfaches Programm, das unter Verwendung einer Instanz von <xref:System.Collections.Generic.List%601>-Typen ausgeführt wird:

using System;
using System.Collections.Generic;

namespace GenericsSampleShort
{
    public static void Main(string[] args)
    {
        // List<string> is the client way of specifying the actual type for the type parameter T
        List<string> listOfStrings = new List<string> { "First", "Second", "Third" };

        // listOfStrings can accept only strings, both on read and write.
        listOfStrings.Add("Fourth");

        // Below will throw a compile-time error, since the type parameter
        // specifies this list as containing only strings.
        listOfStrings.Add(1);
    }
}

Weitere Informationen finden Sie im Thema Generische Typen (Generika) – Übersicht.

Asynchrone Programmierung

Die asynchrone Programmierung ist ein erstklassiges Konzept in .NET und bietet Unterstützung für asynchrone Vorgänge in der Runtime, in den Frameworkbibliotheken und in den .NET-Sprachkonstrukten. Intern basiert sie auf Objekten (z.B. Task), die davon profitieren, dass das Betriebssystem E/A-gebundene Aufträge so effizient wie möglich ausführt.

Erste Informationen über die asynchrone Programmierung in .NET finden Sie im Thema Async (Übersicht).

Sprachintegrierte Abfrage (Language-Integrated Query, LINQ)

LINQ ist ein Satz leistungsstarker Features für C# und VB, mit denen Sie einfachen, deklarativen Code für Datenvorgänge schreiben können. Die Daten können in vielfältiger Form vorliegen (als In-Memory-Objekte, in einer SQL-Datenbank oder in einem XML-Dokument), aber der LINQ-Code weicht in der Regel nicht für die verschiedenen Datenquellen ab.

Weitere Informationen sowie einige Beispiele finden Sie im Thema LINQ (Language Integrated Query).

Native Interoperabilität

Jedes Betriebssystem umfasst eine Anwendungsprogrammierschnittstelle (Application Programming Interface, API), die Systemdienste bereitstellt. In .NET gibt es verschiedene Möglichkeiten, diese APIs aufzurufen.

Die wichtigste Methode, um native Interoperabilität zu erreichen, erfolgt über einen Plattformaufruf oder „P/Invoke“, der auf Linux- und Windows-Plattformen unterstützt wird. Eine nur unter Windows verfügbare Methode zum Erreichen nativer Interoperabilität wird als „COM interop“ bezeichnet und zur Arbeit mit COM-Komponenten in verwaltetem Code verwendet. Die Methode basiert auf der P/Invoke-Infrastruktur, funktioniert jedoch etwas anders.

Der Großteil der Interoperabilitätsunterstützung von Mono (und damit auch von Xamarin) für Java und Objective-C ist gleich aufgebaut, verwendet also die gleichen Prinzipien.

Weitere Informationen dazu finden Sie im Thema Native Interoperabilität.

Unsicherer Code

Je nach Sprachunterstützung können Sie mit der CLR auf nativen Speicher zugreifen und Zeigerarithmetik über unsafe-Code ausführen. Diese Vorgänge werden für bestimmte Algorithmen sowie für die Systeminteroperabilität benötigt. Unsicherer Code ist zwar leistungsstark, aber von seiner Verwendung wird abgeraten, sofern er nicht für die Interoperabilität mit System-APIs oder zur Implementierung des effizientesten Algorithmus erforderlich ist. Unsicherer Code wird in verschiedenen Umgebungen möglicherweise unterschiedlich ausgeführt und bietet weder die Vorteile des Garbage Collectors noch Typsicherheit. Es wird empfohlen, die Verwendung von unsicherem Code so weit wie möglich einzugrenzen und den Code sehr gründlich zu testen.

Das folgende Beispiel zeigt die geänderte Version der ToString()-Methode aus der StringBuilder-Klasse. Es veranschaulicht, wie sich durch Verwendung von unsafe-Code effizient ein Algorithmus implementieren lässt, indem Arbeitsspeicherblöcke direkt verschoben werden:

public override String ToString()
{
    if (Length == 0)
        return String.Empty;

    string ret = string.FastAllocateString(Length);
    StringBuilder chunk = this;
    unsafe
    {
        fixed (char* destinationPtr = ret)
        {
            do
            {
                if (chunk.m_ChunkLength > 0)
                {
                    // Copy these into local variables so that they are stable even in the presence of ----s (hackers might do this)
                    char[] sourceArray = chunk.m_ChunkChars;
                    int chunkOffset = chunk.m_ChunkOffset;
                    int chunkLength = chunk.m_ChunkLength;

                    // Check that we will not overrun our boundaries.
                    if ((uint)(chunkLength + chunkOffset) <= ret.Length && (uint)chunkLength <= (uint)sourceArray.Length)
                    {
                        fixed (char* sourcePtr = sourceArray)
                            string.wstrcpy(destinationPtr + chunkOffset, sourcePtr, chunkLength);
                    }
                    else
                    {
                        throw new ArgumentOutOfRangeException("chunkLength", Environment.GetResourceString("ArgumentOutOfRange_Index"));
                    }
                }
                chunk = chunk.m_ChunkPrevious;
            } while (chunk != null);
        }
    }
    return ret;
}

Nächste Schritte

Wenn Sie sich für eine Einführung in die C#-Features interessieren, lesen Sie Einführung in C#.

Wenn Sie sich für eine Einführung in die F#-Features interessieren, werfen Sie einen Blick auf die Einführung in F#.

Wenn Sie damit beginnen möchten, eigenen Code zu schreiben, lesen Sie Erste Schritte.

Weitere Informationen zu wichtigen Komponenten von .NET finden Sie unter .NET-Architekturkomponenten.