Wbudowane typy odwołań (odwołanie w C#)

Język C# ma wiele wbudowanych typów odwołań. Mają słowa kluczowe lub operatory, które są synonimami typu w bibliotece .NET.

Typ obiektu

Typ object jest aliasem dla System.Object platformy .NET. W ujednoliconym systemie języka C#, wszystkie typy, wstępnie zdefiniowane i zdefiniowane przez użytkownika, typy referencyjne i typy wartości dziedziczą bezpośrednio lub pośrednio z System.Objectklasy . Wartości dowolnego typu można przypisać do zmiennych typu object. Dowolną object zmienną można przypisać do jej wartości domyślnej przy użyciu literału null. Gdy zmienna typu wartości jest konwertowana na obiekt, mówi się, że ma być w polu. Gdy zmienna typu object jest konwertowana na typ wartości, mówi się, że jest rozpakowana. Aby uzyskać więcej informacji, zobacz Boxing and Unboxing (Boxing and Unboxing).

Typ ciągu

Typ string reprezentuje sekwencję zero lub więcej znaków Unicode. string jest aliasem dla System.String platformy .NET.

Chociaż string jest typem odwołania, operatory == równości i != są definiowane w celu porównania wartości string obiektów, a nie odwołań. Równość oparta na wartości sprawia, że testowanie równości ciągów jest bardziej intuicyjne. Na przykład:

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

W poprzednim przykładzie jest wyświetlana wartość "True", a następnie "False", ponieważ zawartość ciągów jest równoważna, ale ab nie odwołuje się do tego samego wystąpienia ciągu.

Operator + łączy ciągi:

string a = "good " + "morning";

Powyższy kod tworzy obiekt ciągu zawierający "dzień dobry".

Ciągi są niezmienne — nie można zmienić zawartości obiektu ciągu po utworzeniu obiektu. Na przykład podczas pisania tego kodu kompilator faktycznie tworzy nowy obiekt ciągu do przechowywania nowej sekwencji znaków, a nowy obiekt jest przypisany do b. Pamięć przydzielona b (gdy zawierała ciąg "h"), kwalifikuje się do odzyskiwania pamięci.

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

Operator [] może służyć do odczytu dostępu do poszczególnych znaków ciągu. Prawidłowe wartości indeksu zaczynają się od 0 i muszą być mniejsze niż długość ciągu:

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

W podobny sposób [] operator może być również używany do iterowania poszczególnych znaków w ciągu:

string str = "test";

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

Literały ciągu

Literały ciągu są typu string i mogą być zapisywane w trzech formach, nieprzetworzonych, cytowanych i dosłownych.

Nieprzetworzone literały ciągów są dostępne począwszy od języka C# 11. Nieprzetworzone literały ciągu mogą zawierać dowolny tekst bez konieczności sekwencji ucieczki. Nieprzetworzone literały ciągu mogą zawierać białe znaki i nowe wiersze, osadzone cudzysłowy i inne znaki specjalne. Nieprzetworzone literały ciągu są ujęte w co najmniej trzy podwójne cudzysłowy (""):

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

Można nawet dołączyć sekwencję trzech (lub więcej) znaków podwójnego cudzysłowu. Jeśli tekst wymaga osadzonej sekwencji cudzysłowów, zaczynasz i kończysz nieprzetworzony literał ciągu z większą częścią cudzysłowu, zgodnie z potrzebami:

"""""
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.
"""""

Nieprzetworzone literały ciągu zwykle mają sekwencje cudzysłowu początkowego i końcowego w osobnych wierszach od osadzonego tekstu. Wielowierszowe literały ciągów nieprzetworzonych obsługują ciągi, które same są ciągami cytowania:

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

Gdy cudzysłowy początkowe i końcowe znajdują się w osobnych wierszach, nowe linie po cudzysłowie otwierającym i poprzedzającym cudzysłów końcowych nie są uwzględniane w końcowej zawartości. Sekwencja cudzysłowu zamykającego określa lewą kolumnę literału ciągu. Możesz wciąć nieprzetworzony literał ciągu, aby był zgodny z ogólnym formatem kodu:

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

Kolumny po prawej stronie sekwencji cudzysłowu końcowego są zachowywane. To zachowanie umożliwia nieprzetworzone ciągi dla formatów danych, takich jak JSON, YAML lub XML, jak pokazano w poniższym przykładzie:

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

Kompilator zgłasza błąd, jeśli którykolwiek z wierszy tekstu rozciąga się po lewej stronie sekwencji cudzysłowu zamykającego. Sekwencje cudzysłowu otwierającego i zamykającego mogą znajdować się w tym samym wierszu, co oznacza, że literał ciągu nie rozpoczyna się ani kończy znakiem cudzysłowu:

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

Możesz połączyć nieprzetworzone literały ciągu z interpolacją ciągów, aby uwzględnić znaki cudzysłowu i nawiasy klamrowe w ciągu wyjściowym.

Literały ciągów cytowanych są ujęte w znaki podwójnego cudzysłowu ("):

"good morning"  // a string literal

Literały ciągu mogą zawierać dowolny literał znaku. Sekwencje ucieczki są uwzględniane. W poniższym przykładzie użyto sekwencji \\ ucieczki ukośnika odwrotnego, \u0066 litery f i \n nowego wiersza.

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

Uwaga

Kod \udddd ucieczki (gdzie dddd jest liczbą czterocyfrową) reprezentuje znak Unicode U+dddd. Rozpoznano również osiem cyfrowych kodów ucieczki Unicode: \Udddddddd.

Literały ciągu dosłownego zaczynają się od @ i są również ujęte w znaki podwójnego cudzysłowu. Na przykład:

@"good morning"  // a string literal

Zaletą ciągów dosłownych jest to, że sekwencje ucieczki nie są przetwarzane, co ułatwia pisanie. Na przykład następujący tekst pasuje do w pełni kwalifikowanej nazwy pliku systemu Windows:

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

Aby uwzględnić podwójny cudzysłów w ciągu @-quoted, podwaja go:

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

Literały ciągu UTF-8

Ciągi na platformie .NET są przechowywane przy użyciu kodowania UTF-16. UTF-8 jest standardem dla protokołów sieci Web i innych ważnych bibliotek. Począwszy od języka C# 11, można dodać u8 sufiks do literału ciągu, aby określić kodowanie UTF-8. Literały UTF-8 są przechowywane jako ReadOnlySpan<byte> obiekty. Naturalnym typem literału ciągu UTF-8 jest ReadOnlySpan<byte>. Użycie literału ciągu UTF-8 tworzy bardziej wyraźną deklarację niż deklarowanie równoważnego System.ReadOnlySpan<T>, jak pokazano w poniższym kodzie:

ReadOnlySpan<byte> AuthWithTrailingSpace = new byte[] { 0x41, 0x55, 0x54, 0x48, 0x20 };
ReadOnlySpan<byte> AuthStringLiteral = "AUTH "u8;

Aby zapisać literał ciągu UTF-8 jako tablicę, należy użyć polecenia ReadOnlySpan<T>.ToArray() , aby skopiować bajty zawierające literał do tablicy modyfikowalnej:

byte[] AuthStringLiteral = "AUTH "u8.ToArray();

Literały ciągu UTF-8 nie są stałymi czasu kompilacji; są stałymi środowiska uruchomieniowego. W związku z tym nie można ich użyć jako wartości domyślnej dla opcjonalnego parametru. Literały ciągów UTF-8 nie mogą być łączone z interpolacją ciągów. Nie można użyć tokenu $ i sufiksu w tym samym wyrażeniu u8 ciągu.

Typ delegata

Deklaracja typu delegata jest podobna do podpisu metody. Ma wartość zwracaną i dowolną liczbę parametrów dowolnego typu:

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

Na platformie .NET System.Action i System.Func typy zawierają definicje ogólne dla wielu typowych delegatów. Prawdopodobnie nie trzeba definiować nowych niestandardowych typów delegatów. Zamiast tego można tworzyć wystąpienia podanych typów ogólnych.

A delegate to typ odwołania, który może służyć do hermetyzacji nazwanej lub anonimowej metody. Delegaty są podobne do wskaźników funkcji w języku C++; jednak delegaty są bezpieczne i bezpieczne. Aby uzyskać informacje o aplikacjach delegatów, zobacz Delegaty i Delegaty ogólne. Delegaty są podstawą dla zdarzeń. Pełnomocnik można utworzyć wystąpienie, kojarząc go z nazwaną lub anonimową metodą.

Delegat musi zostać utworzone za pomocą metody lub wyrażenia lambda, które ma zgodny typ zwracany i parametry wejściowe. Aby uzyskać więcej informacji na temat stopnia wariancji dozwolonego w podpisie metody, zobacz Variance in Delegates (Wariancja w delegatach). Aby używać z metodami anonimowymi, delegat i kod, który ma być skojarzony z nim, są deklarowane razem.

Delegowanie kombinacji lub usuwania kończy się niepowodzeniem z wyjątkiem środowiska uruchomieniowego, gdy typy delegatów biorące udział w czasie wykonywania różnią się z powodu konwersji wariantu. W poniższym przykładzie pokazano sytuację, która kończy się niepowodzeniem:

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;

Delegata można utworzyć z poprawnym typem środowiska uruchomieniowego, tworząc nowy obiekt delegata. W poniższym przykładzie pokazano, jak można zastosować to obejście do poprzedniego przykładu.

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;

Można zadeklarować wskaźniki funkcji, które używają podobnej składni. Wskaźnik funkcji używa calli instrukcji zamiast tworzenia wystąpienia typu delegata i wywoływania metody wirtualnej Invoke .

Typ dynamiczny

Typ dynamic wskazuje, że użycie zmiennej i odwołań do jej elementów członkowskich pomija sprawdzanie typu kompilatora czasu. Zamiast tego te operacje są rozwiązywane w czasie wykonywania. dynamic Typ upraszcza dostęp do interfejsów API MODELU COM, takich jak interfejsy API usługi Office Automation, do dynamicznych interfejsów API, takich jak biblioteki IronPython, oraz do modelu DOM (DOCUMENT Object Model).

Typ dynamic zachowuje się jak typ object w większości okoliczności. W szczególności dowolne wyrażenie inne niż null można przekonwertować na dynamic typ. Typ dynamic różni się od object tych operacji, które zawierają wyrażenia typu dynamic , nie są rozpoznawane ani typ sprawdzany przez kompilator. Kompilator pakuje razem informacje o operacji, a informacje te są później używane do oceny operacji w czasie wykonywania. W ramach procesu zmienne typu dynamic są kompilowane w zmienne typu object. W związku z tym typ dynamic istnieje tylko w czasie kompilacji, a nie w czasie wykonywania.

Poniższy przykład kontrastuje zmienną typu dynamic ze zmienną typu object. Aby sprawdzić typ każdej zmiennej w czasie kompilacji, umieść wskaźnik dyn myszy na lub obj w instrukcjach WriteLine . Skopiuj następujący kod do edytora, w którym jest dostępna funkcja IntelliSense. Funkcja IntelliSense wyświetla dynamiczne obiekty dyn idla elementu 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());
    }
}

Instrukcje WriteLine wyświetlają typy dyn czasu wykonywania i obj. W tym momencie oba mają ten sam typ, liczbę całkowitą. Zostaną wyświetlone następujące dane wyjściowe:

System.Int32
System.Int32

Aby zobaczyć różnicę między dyn i obj w czasie kompilacji, dodaj następujące dwa wiersze między deklaracjami a WriteLine instrukcjami w poprzednim przykładzie.

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

Zgłaszany jest błąd kompilatora dla próby dodania liczby całkowitej i obiektu w wyrażeniu obj + 3. Jednak dla elementu dyn + 3nie zgłoszono żadnego błędu . Wyrażenie, które zawiera dyn , nie jest sprawdzane w czasie kompilacji, ponieważ typ dyn to dynamic.

W poniższym przykładzie użyto dynamic kilku deklaracji. Metoda Main kontrastuje również sprawdzanie typu czasu kompilacji przy użyciu sprawdzania typów czasu wykonywania.

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

specyfikacja języka C#

Aby uzyskać więcej informacji, zobacz następujące sekcje specyfikacji języka C#:

Zobacz też