Wbudowane typy referencyjne (odwołanie w C#)

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

Typ obiektu

Typ object jest aliasem dla nazwy System.Object na platformie .NET. W ujednoliconym systemie typów 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.Object . 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, jest ona mówiona o polu . Gdy zmienna typu jest konwertowana na typ wartości, jest ona mówiona jako object rozpakowana. Aby uzyskać więcej informacji, zobacz Boxing and Unboxing (Boxing and Unboxing).

Typ ciągu

Typ string reprezentuje sekwencję zerową lub większą liczbę znaków Unicode. string jest aliasem dla System.String w .NET.

Mimo string że jest typem odwołania, operatory równości == i są != definiowane w celu porównywania wartości string obiektów, a nie odwołań. Dzięki temu 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));

Powoduje to wyświetlenie wartości "True", a następnie "False", ponieważ zawartość ciągów jest równoważna, ale i nie odwołują a się do tego samego wystąpienia b ciągu.

Operator + łączy ciągi:

string a = "good " + "morning";

Powoduje to utworzenie obiektu ciągu zawierającego "dzień dobry".

Ciągi są niezmienne— nie można zmienić zawartości obiektu ciągu po utworzeniu obiektu, chociaż składnia sprawia, że wygląda tak, jakby można to zrobić. Na przykład podczas pisania tego kodu kompilator tworzy nowy obiekt ciągu do przechowywania nowej sekwencji znaków i ten nowy obiekt jest przypisywany do b . Pamięć, która została przydzielona do (gdy zawierała ciąg "h"), kwalifikuje się do b odśmiecania pamięci.

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

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

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

Podobnie operator może być również używany do [] iterowania po poszczególnych znakach 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 są typu i mogą być zapisywane w dwóch formach, w string cudzysłowach @ i -quoted. Literały ciągu w cudzysłowie są ujęte w podwójne cudzysłowy ("):

"good morning"  // a string literal

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

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

Uwaga

Kod ucieczki \udddd (gdzie dddd jest liczbą czterocyfrową) reprezentuje znak Unicode U+ dddd . Rozpoznawane są również 8-cyfrowe kody 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 w pełni kwalifikowanej Windows nazwy pliku:

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

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

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

Typ delegata

Deklaracja typu delegata jest podobna do sygnatury 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 System.Func typy i zapewniają 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 jest typem referencyjnym, który może służyć do hermetyzacji metody nazwanej lub anonimowej. Delegaty są podobne do wskaźników funkcji w języku C++; Delegaty są jednak bezpieczne pod tym typem. Aby uzyskać informacje o aplikacjach delegatów, zobacz Delegaty i Delegaty ogólne. Delegaty są podstawą dla zdarzeń. Delegata można utworzyć przez skojarzenie go z metodą nazwaną lub anonimową.

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

Kombinacja delegatów i usuwanie kończy się niepowodzeniem z powodu wyjątku środowiska uruchomieniowego, gdy typy delegatów zaangażowane w czasie wykonywania różnią się z powodu konwersji wariantów. 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 runtime.
Action<string> combination = stringAction + objectAction;

Możesz utworzyć delegata z poprawnym typem środowiska uruchomieniowego, tworząc nowy obiekt delegowany. W poniższym przykładzie pokazano, jak to obejście można zastosować 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;

Począwszy od języka C# 9, można deklarować wskaźniki funkcji, które używają podobnej składni. Wskaźnik funkcji używa instrukcji zamiast wystąpienia typu delegata calli i wywoływania metody Invoke wirtualnej.

Typ dynamiczny

Typ wskazuje, że użycie zmiennej i odwołania do jej elementów członkowskich dynamic pomijają sprawdzanie typu w czasie kompilacji. Zamiast tego te operacje są rozwiązywane w czasie działania. Ten 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 interfejsu dynamic HTML Document Object Model (DOM).

Typ dynamic zachowuje się jak typ w większości object przypadków. W szczególności dowolne wyrażenie inne niż null może zostać przekonwertowane na dynamic typ . Typ różni się od tego, że operacje zawierające wyrażenia typu nie są rozpoznawane lub dynamic object dynamic sprawdzane przez kompilator. Kompilator pakuje razem informacje o operacji, a te informacje są później używane do oceny operacji w czasie działania. W ramach procesu zmienne typu są kompilowane dynamic do zmiennych typu object . W związku z tym dynamic typ istnieje tylko w czasie kompilacji, a nie w czasie uruchamiania.

Poniższy przykład kontrastuje zmienną typu dynamic ze zmienną typu object . Aby sprawdzić typ każdej zmiennej w czasie kompilacji, umieść wskaźnik myszy nad dyn lub obj w WriteLine instrukcjach. Skopiuj następujący kod do edytora, w którym jest dostępna funkcja IntelliSense. Funkcja IntelliSense pokazuje dynamiczny obiekt dyn dla i obj dla .

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 czasu uruchamiania elementów dyn i obj . W tym momencie oba typy 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 i w czasie kompilacji, dodaj następujące dwa wiersze między deklaracjami i dyn obj WriteLine instrukcje w poprzednim przykładzie.

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

Zgłaszany jest błąd kompilatora dla próby dodatku liczby całkowitej i obiektu w wyrażeniu obj + 3 . Nie jest jednak zgłaszany żaden błąd dla dyn + 3 . Wyrażenie, które dyn zawiera, nie jest sprawdzane w czasie kompilacji, ponieważ typ dyn to dynamic .

W poniższym przykładzie użyto dynamic w kilku deklaracjach. Metoda kontrastuje również sprawdzanie typów w czasie kompilacji z Main sprawdzaniem typów w czasie działania.

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

Zobacz też