Einführung in .NET

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

Dieser Artikel bietet eine Einführung in einige der wichtigsten Features der .NET-Plattform.

Unter .NET-Architekturkomponenten finden Sie Informationen zu den einzelnen Bestandteilen der Architektur und deren Verwendung.

Ausführen der Codebeispiele

Informationen zum Einrichten einer Entwicklungsumgebung zum Ausführen der Codebeispiele finden Sie unter Erste Schritte. Sie können Codebeispiele auf dieser Seite kopieren und zum Ausführen in Ihrer Umgebung einfügen.

Hinweis

Zukünftig wird diese Dokumentationswebsite die Möglichkeit bieten, diese Codebeispiele in Ihrem Browser auszuführen.

Programmiersprachen

.NET unterstützt mehrere Programmiersprachen. .NET-Runtimes 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 und unterstützt aktiv drei .NET-Sprachen: C#, F# und Visual Basic .NET.

  • 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, wird wenig Probleme bei der Verwendung von C# haben. 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 Anwendungen erstellen können, die in .NET ausgeführt werden.

Automatische Speicherverwaltung

.NET verwendet Garbage Collection, um eine automatische Speicherverwaltung für Programme bereitzustellen. Der Garbage Collector arbeitet bei der Speicherverwaltung mit dem Prinzip der Verzögerung und gibt dem Anwendungsdurchsatz 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. Das unveränderliche Merkmal der Speichersicherheit ist sehr einfach: Ein Programm ist speichersicher, wenn es nur auf Arbeitsspeicher zugreift, der zugewiesen (nicht freigegeben) wurde. Die Laufzeit stellt z.B. sicher, dass Programme keine Indizierung über das Ende eines Arrays hinaus durchführen und nicht auf Phantomfelder hinter dem Ende eines Objekts zugreifen.

Im folgenden Beispiel löst die Laufzeit 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-Laufzeit verwaltet werden. Ein Dateihandle ist z.B. eine nicht verwaltete Ressource. Ein @System.IO.FileStream-Objekt ist ein verwaltetes Objekt, aber es verweist auf ein Dateihandle, das nicht verwaltet ist. Wenn Sie mit FileStream fertig sind, müssen Sie das Dateihandle freigeben.

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

using System.IO;

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

Sobald der using-Block abgeschlossen ist, ruft die .NET-Laufzeit automatisch die @System.IDisposable.Dispose-Methode des stream-Objekts auf, die das Dateihandle freigibt. Die Laufzeit wird dies ebenfalls tun, wenn eine Ausnahme das Steuerelement dazu veranlasst, den Block zu verlassen.

Weitere Details finden Sie auf den folgenden Seiten:

Typsicherheit

Objekte werden in Form von Typen zugeordnet. Die einzigen Vorgänge, die für ein bestimmtes Objekt zulässig sind, und der Arbeitsspeicher, den dieses Objekt belegt, gehören zum Typ des Objekts. Ein Dog-Typ kann Jump- und WagTail-Methoden besitzen, wahrscheinlich aber keine SumTotal-Methode. Ein Programm kann nur die deklarierten Methoden eines bestimmten Typs aufrufen. Alle anderen Aufrufe führen entweder zu einem Fehler während der Kompilierung oder zu 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 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#, Visual Basic und F# unterstützen einen lokalen Typrückschluss. 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. Wir schreiben die ersten beiden Zeilen des vorherigen Beispiels neu, um einen Typrückschluss einzuführen. Beachten Sie, dass der Rest des Beispiels vollständig gleich bleibt.

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 den Typrückschluss innerhalb einer Methode wie in C# und Visual Basic. Weitere Informationen finden Sie unter Typrückschluss.

Delegaten und Lambdas

Delegaten ähneln C++-Funktionszeigern, mit dem großen Unterschied, dass sie typsicher sind. 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 verschiedenen APIs und an anderen Stellen in der .NET-Welt verwendet, insbesondere über Lambdaausdrücke, die ein Kernstück von LINQ sind.

Weitere Informationen hierzu finden Sie im Dokument Delegaten und Lambdas.

Generika

Generika wurden in .NET Framework 2.0 ergänzt. Kurz gesagt: 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. Das führte zu verschiedenen Probleme hinsichtlich der Leistung und der Semantik, von möglichen Laufzeitfehlern ganz zu schweigen. 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 @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 Artikel Generische Typen (Generika) – Übersicht.

Asynchrone Programmierung

Die asynchrone Programmierung ist ein erstklassiges Konzept in .NET und bietet Unterstützung für asynchrone Vorgänge während der Laufzeit, 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 Jobs so effizient wie möglich ausführt.

Erste Informationen über die asynchrone Programmierung in .NET finden Sie unter 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 unterscheidet sich in der Regel für die verschiedenen Datenquellen nicht.

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

Native Interoperabilität

Jedes heute verwendete Betriebssystem stellt Plattformunterstützung für verschiedene Aufgaben der Programmierung bereit. .NET bietet verschiedene Möglichkeiten, diese APIs zu nutzen. Diese Unterstützung wird als „native Interoperabilität“ bezeichnet, und in diesem Abschnitt geht es darum, wie mit verwaltetem .NET-Code auf native APIs zugegriffen werden kann.

Die wichtigste Methode, um native Interoperabilität zu erreichen, erfolgt über einen Plattformaufruf: „P/Invoke“. Die Unterstützung in .NET Core steht für Linux- und Windows-Plattformen zur Verfügung. Eine andere, 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 setzt auf der P/Invoke-Infrastruktur auf, 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 Dokument Native Interoperabilität.

Unsicherer Code

Die CLR ermöglicht die Fähigkeit, über unsafe-Code auf nativen Arbeitsspeicher zuzugreifen und Zeigerarithmetik durchzufü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 auch nicht die Vorteile von Garbage Collector und Typsicherheit. Es wird empfohlen, die Verwendung von unsicherem Code so weit wie möglich eingrenzen 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, lesen Sie 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.