Integrierte Verweistypen (C#-Referenz)

C# verfügt über viele integrierte Referenztypen. Diese enthalten Schlüsselwörter oder Operatoren, die Synonyme für einen Typ in der .NET-Bibliothek sind.

den Objekttyp

Der object-Typ ist ein Alias für System.Object in .NET. Im vereinheitlichen Typsystem von C# erben alle Typen, vordefiniert und benutzerdefiniert sowie Verweis- und Werttypen, direkt oder indirekt von System.Object. Sie können Werte eines beliebigen Typs Variablen des Typs object zuweisen. Diesem Standardwert kann mithilfe des Literals null eine beliebige object-Variable zugewiesen werden. Wenn eine Variable eines Werttyps in ein Objekt konvertiert wird, wird es gesagt, dass er boxt wird. Wenn eine Variable des Typs in einen Werttyp object konvertiert wird, ist es gesagt, dass es nicht posteingangsgeboxt ist. Weitere Informationen finden Sie unter Boxing und Unboxing.

Der Zeichenfolgentyp

Der Typ string stellt eine Sequenz von Null oder mehr Unicode-Zeichen dar. string ist ein Alias für System.String in .NET.

Obwohl string ein Verweistyp ist, werden die Gleichheitsoperatoren== und != zum Vergleichen der Werte von string-Objekten, nicht von Verweisen, definiert. Wertbasierte Gleichheit macht Tests für Zeichenfolgengleichheit intuitiver. Zum Beispiel:

string a = "hello";
string b = "h";
// Append to contents of 'b'
b += "ello";
Console.WriteLine(a == b);
Console.WriteLine(object.ReferenceEquals(a, b));

Im vorherigen Beispiel wird "True" und dann "False" angezeigt, da der Inhalt der Zeichenfolgen gleichwertig ist, aber ab nicht auf die gleiche Zeichenfolgeninstanz verweist.

Der Operator „+“ verkettet Zeichenfolgen:

string a = "good " + "morning";

Der vorherige Code erstellt ein Zeichenfolgenobjekt, das "guten Morgen" enthält.

Zeichenfolgen sind unveränderlich- der Inhalt eines Zeichenfolgenobjekts kann nach dem Erstellen des Objekts nicht geändert werden, obwohl die Syntax wie möglich angezeigt wird. Wenn Sie z. B. diesen Code schreiben, erstellt der Compiler tatsächlich ein neues Zeichenfolgenobjekt, um die neue Zeichensequenz zu speichern. Das neue Objekt wird b zugewiesen. Der Speicherplatz, der b zugeordnet wurde (sofern die Zeichenfolge „h“ enthalten war), hat dann Anspruch auf die Garbage Collection.

string b = "h";
b += "ello";

Der []Operator kann für den Lesezugriff auf einzelne Zeichen einer Zeichenfolge verwendet werden. Gültige Indexwerte beginnen bei 0 und müssen kleiner als die Länge der Zeichenfolge sein:

string str = "test";
char x = str[2];  // x = 's';

Auf gleiche Weise kann der []-Operator auch für das Durchlaufen jedes Zeichens in der Zeichenfolge verwendet werden:

string str = "test";

for (int i = 0; i < str.Length; i++)
{
  Console.Write(str[i] + " ");
}
// Output: t e s t

Zeichenfolgenliterale

Zeichenfolgen literale sind vom Typ string und können in drei Formularen, roh, zitiert und verbatim geschrieben werden.

Rohe Zeichenfolgen literale sind ab C# 11 verfügbar. Unformatierte Zeichenfolgen-Literale können beliebiger Text enthalten, ohne dass Escapesequenzen erforderlich sind. Unformatierte Zeichenfolgen-Literale können Leerzeichen und neue Zeilen, eingebettete Anführungszeichen und andere Sonderzeichen enthalten. Unformatierte Zeichenfolgen literale werden in mindestens drei doppelte Anführungszeichen ("""") eingeschlossen:

"""
This is a multi-line
    string literal with the second line indented.
"""

Sie können sogar eine Sequenz von drei (oder mehr) doppelten Anführungszeichen enthalten. Wenn Ihr Text eine eingebettete Sequenz von Anführungszeichen erfordert, beginnen Und beenden Sie die unformatierte Zeichenfolge literal mit weiteren Anführungszeichen, wie erforderlich:

"""""
This raw string literal has four """", count them: """" four!
embedded quote characters in a sequence. That's why it starts and ends
with five double quotes.

You could extend this example with as many embedded quotes as needed for your text.
"""""

Unformatierte Zeichenfolgenliterale verfügen in der Regel über die Start- und Endführungssequenzen auf separaten Zeilen aus dem eingebetteten Text. Multiline-rohe Zeichenfolgen literale unterstützen Zeichenfolgen, die sich selbst zitierte Zeichenfolgen sind:

var message = """
"This is a very important message."
""";
Console.WriteLine(message);
// output: "This is a very important message."

Wenn sich die Start- und Endzeichen auf separaten Zeilen befinden, werden die neuen Linien nach dem Öffnen des Anführungszeichens und vor dem Ende des Anführungszeichens nicht in den endgültigen Inhalt eingeschlossen. Die schließende Anführungssequenz diktiert die linke Spalte für die Zeichenfolge Literal. Sie können einen unformatierten Zeichenfolgen literal einziehen, um dem gesamten Codeformat zu entsprechen:

var message = """
    "This is a very important message."
    """;
Console.WriteLine(message);
// output: "This is a very important message."
// The leftmost whitespace is not part of the raw string literal

Spalten rechts neben der endenden Anführungszeichensequenz bleiben erhalten. Dies ermöglicht unformatierte Zeichenfolgen für Datenformate wie JSON, YAML oder XML, wie im folgenden Beispiel gezeigt:

var json= """
    {
        "prop": 0
    }
    """;

Der Compiler gibt einen Fehler an, wenn eine der Textzeilen links neben der schließenden Anführungssequenz erweitert wird. Die öffnenden und schließenden Anführungssequenzen können sich in derselben Zeile befinden, indem die Zeichenfolge literal weder gestartet noch mit einem Anführungszeichen endet:

var shortText = """He said "hello!" this morning.""";

Sie können unformatierte Zeichenfolgen literale mit Zeichenfolgeninterpolation kombinieren, um Anführungszeichen und Klammern in die Ausgabezeichenfolge einzuschließen.

Zeichenfolgenliterale in Anführungszeichen werden in doppelte Anführungszeichen (") eingeschlossen:

"good morning"  // a string literal

Zeichenfolgenliterale können jeden Zeichenliteral enthalten. Escapesequenzen sind enthalten. Im folgenden Beispiel wird die Escapesequenz \\ für den umgekehrten Schrägstrich, \u0066 für den Buchstaben „f“ und \n für den Zeilenumbruch verwendet.

string a = "\\\u0066\n F";
Console.WriteLine(a);
// Output:
// \f
//  F

Hinweis

Der Escapecode \udddd (wobei dddd eine vierstellige Zahl ist) stellt das Unicode-Zeichen U+dddd dar. Escapecodes aus achtstelligen Unicode werden auch erkannt: \Udddddddd.

Wörtliche Zeichenfolgenliterale beginnen mit @ und sind ebenfalls in doppelte Anführungszeichen eingeschlossen. Beispiel:

@"good morning"  // a string literal

Der Vorteil von Verbatim-Zeichenfolgen besteht darin, dass Escapesequenzen nicht verarbeitet werden , was das Schreiben erleichtert. Der folgende Text entspricht beispielsweise einem vollqualifizierten Windows Dateinamen:

@"c:\Docs\Source\a.txt"  // rather than "c:\\Docs\\Source\\a.txt"

Um ein Doppeltes Anführungszeichen in eine @-anführungszeichenfolge einzuschließen, doppelten Sie folgendes:

@"""Ahoy!"" cried the captain." // "Ahoy!" cried the captain.

Der Delegattyp

Die Deklaration eines Delegattyps ähnelt einer Methodensignatur. Er verfügt über einen Rückgabewert und eine beliebige Anzahl Parameter eines beliebigen Typs:

public delegate void MessageDelegate(string message);
public delegate int AnotherDelegate(MyType m, long num);

In .NET stellen die Typen System.Action und System.Func generische Definitionen für zahlreiche allgemeine Delegaten bereit. Sie werden sehr wahrscheinlich keine neuen benutzerdefinierten Delegattypen definieren müssen. Stattdessen können Sie Instanziierungen der bereitgestellten generischen Typen erstellen.

Ein delegate ist ein Verweistyp, der verwendet werden kann, um eine benannte oder anonyme Methode zu kapseln. Delegaten entsprechen den Funktionszeigern in C++, sind jedoch typsicher und geschützt. Anwendungsmöglichkeiten von Delegaten finden Sie unter Delegaten und Generische Delegaten. Delegaten bilden die Grundlage für Ereignisse. Ein Delegat kann instanziiert werden, entweder durch Zuordnen mit einer benannten oder einer anonymen Methode.

Der Delegat muss mit einer Methode oder einem Lambda-Ausdruck instanziiert werden, der über einen kompatiblen Rückgabetypen und Eingabeparameter verfügt. Weitere Informationen zum Grad der Varianz, der in der Methodensignatur zulässig ist, finden Sie unter Varianz bei Delegaten. Für die Verwendung mit anonymen Methoden werden der Delegat und der Code, der mit ihm zugeordnet werden soll, zusammen deklariert.

Das Kombinieren und Entfernen von Delegaten schlägt mit einer Laufzeitausnahme fehl, wenn die zur Laufzeit beteiligten Delegattypen aufgrund der Variantenkonvertierung unterschiedlich sind. Im folgenden Beispiel wird eine Situation veranschaulicht, die fehlschlägt:

Action<string> stringAction = str => {};
Action<object> objectAction = obj => {};
  
// Valid due to implicit reference conversion of
// objectAction to Action<string>, but may fail
// at run time.
Action<string> combination = stringAction + objectAction;

Sie können einen Delegaten mit dem richtigen Laufzeittyp erstellen, indem Sie ein neues Delegatobjekt erstellen. Im folgenden Beispiel wird veranschaulicht, wie diese Problemumgehung auf das vorherige Beispiel angewendet werden kann.

Action<string> stringAction = str => {};
Action<object> objectAction = obj => {};
  
// Creates a new delegate instance with a runtime type of Action<string>.
Action<string> wrappedObjectAction = new Action<string>(objectAction);

// The two Action<string> delegate instances can now be combined.
Action<string> combination = stringAction + wrappedObjectAction;

Ab C# 9 können Sie Funktionszeiger deklarieren, die eine ähnliche Syntax verwenden. Ein Funktionszeiger verwendet die calli-Anweisung, anstatt einen Delegattyp zu instanziieren und die virtuelle Invoke-Methode aufzurufen.

Der dynamische Typ

Der dynamic-Typ gibt an, dass durch die Verwendung der Variablen und Verweise auf die entsprechenden Member die Prüfung des Kompilierzeittyps umgangen wird. Stattdessen werden diese Vorgänge zur Laufzeit aufgelöst. Der dynamic-Typ vereinfacht den Zugriff auf COM-APIs, z. B. die Office Automation-APIs, auf dynamische APIs, beispielsweise IronPython-Bibliotheken und auf das HTML-Dokumentobjektmodell (Document Object Model, DOM).

Der Typ dynamic verhält sich in den meisten Fällen wie Typ object. Insbesondere können alle Ausdrücke, die nicht NULL sind, in den dynamic-Typ konvertiert werden. Der dynamic Typ unterscheidet sich von object diesen Vorgängen, die Ausdrücke des dynamic Typs enthalten, werden nicht aufgelöst oder vom Compiler überprüft. Der Compiler packt Informationen über den Vorgang. Diese Informationen werden später zur Evaluierung des Vorgangs zur Laufzeit verwendet. Als Teil dieses Prozesses werden Variablen des Typs dynamic in Variablen des Typs object kompiliert. Deshalb existiert der Typ dynamic nur zur Kompilierzeit und nicht zur Laufzeit.

Das folgende Beispiel vergleicht den Unterschied einer Variable des Typs dynamic mit einer Variable des Typs object. Um den Typ jeder Variable zur Kompilierzeit zu überprüfen, zeigen Sie mit dem Mauszeiger auf dyn oder obj in den WriteLine-Anweisungen. Kopieren Sie den folgenden Code in einen Editor, in dem IntelliSense verfügbar ist. IntelliSense zeigt dynamic für dyn und object für obj.

class Program
{
    static void Main(string[] args)
    {
        dynamic dyn = 1;
        object obj = 1;

        // Rest the mouse pointer over dyn and obj to see their
        // types at compile time.
        System.Console.WriteLine(dyn.GetType());
        System.Console.WriteLine(obj.GetType());
    }
}

Die WriteLine-Anweisungen zeigen die Laufzeittypen von dyn und obj. Zu diesem Zeitpunkt verfügen beide denselben Typ, Integer. Es wird die folgende Ausgabe generiert:

System.Int32
System.Int32

Um den Unterschied zwischen dyn und obj zur Kompilierzeit anzuzeigen, fügen Sie die folgenden zwei Zeilen zwischen die Deklarationen und die WriteLine-Anweisungen im vorherigen Beispiel ein.

dyn = dyn + 3;
obj = obj + 3;

Es wird ein Kompilierfehler für den Versuch, einen Integer und ein Objekt im Ausdruck obj + 3 einzufügen, ausgegeben. Es wird jedoch kein Fehler für dyn + 3 gemeldet. Der Ausdruck, der enthält dyn , wird zur Kompilierungszeit nicht überprüft, da der Typ des dyn Typs ist dynamic.

Das folgende Beispiel verwendet dynamic in einigen Deklarationen. Die Main-Methode unterscheidet auch die Typüberprüfung zur Kompilierzeit und die Laufzeittypüberprüfung.

using System;

namespace DynamicExamples
{
    class Program
    {
        static void Main(string[] args)
        {
            ExampleClass ec = new ExampleClass();
            Console.WriteLine(ec.exampleMethod(10));
            Console.WriteLine(ec.exampleMethod("value"));

            // The following line causes a compiler error because exampleMethod
            // takes only one argument.
            //Console.WriteLine(ec.exampleMethod(10, 4));

            dynamic dynamic_ec = new ExampleClass();
            Console.WriteLine(dynamic_ec.exampleMethod(10));

            // Because dynamic_ec is dynamic, the following call to exampleMethod
            // with two arguments does not produce an error at compile time.
            // However, it does cause a run-time error.
            //Console.WriteLine(dynamic_ec.exampleMethod(10, 4));
        }
    }

    class ExampleClass
    {
        static dynamic field;
        dynamic prop { get; set; }

        public dynamic exampleMethod(dynamic d)
        {
            dynamic local = "Local variable";
            int two = 2;

            if (d is int)
            {
                return local;
            }
            else
            {
                return two;
            }
        }
    }
}
// Results:
// Local variable
// 2
// Local variable

Siehe auch