Szukaj…


Wprowadzenie

Słowa kluczowe są predefiniowanymi, zastrzeżonymi identyfikatorami o specjalnym znaczeniu dla kompilatora. Nie można ich używać jako identyfikatorów w programie bez prefiksu @ . Na przykład @if jest prawnym identyfikatorem, ale nie słowem kluczowym if .

Uwagi

C # ma predefiniowany zbiór „słów kluczowych” (lub słów zastrzeżonych), z których każde ma specjalną funkcję. Te słowa nie mogą być używane jako identyfikatory (nazwy zmiennych, metod, klas itp.), Chyba że poprzedzone są @ .

Oprócz nich C # używa również niektórych słów kluczowych, aby nadać określone znaczenie w kodzie. Są to tak zwane kontekstowe słowa kluczowe. Kontekstowe słowa kluczowe mogą być używane jako identyfikatory i nie muszą być poprzedzane znakiem @ gdy są używane jako identyfikatory.

stackalloc

stackalloc kluczowe stackalloc tworzy region pamięci na stosie i zwraca wskaźnik na początek tej pamięci. Przydzielona pamięć stosu jest automatycznie usuwana po wyjściu z zakresu, w którym została utworzona.

//Allocate 1024 bytes. This returns a pointer to the first byte.
byte* ptr = stackalloc byte[1024];

//Assign some values...
ptr[0] = 109;
ptr[1] = 13;
ptr[2] = 232;
...

Używany w niebezpiecznym kontekście.

Podobnie jak w przypadku wszystkich wskaźników w języku C #, nie ma granic sprawdzających odczyty i przypisania. Czytanie poza granicami przydzielonej pamięci przyniesie nieprzewidywalne wyniki - może uzyskać dostęp do dowolnego dowolnego miejsca w pamięci lub może spowodować wyjątek naruszenia dostępu.

//Allocate 1 byte
byte* ptr = stackalloc byte[1];

//Unpredictable results...
ptr[10] = 1;
ptr[-1] = 2;

Przydzielona pamięć stosu jest automatycznie usuwana po wyjściu z zakresu, w którym została utworzona. Oznacza to, że nigdy nie powinieneś zwracać pamięci utworzonej za pomocą stackalloc ani przechowywać jej poza okresem użytkowania zakresu.

unsafe IntPtr Leak() {
    //Allocate some memory on the stack
    var ptr = stackalloc byte[1024];

    //Return a pointer to that memory (this exits the scope of "Leak")
    return new IntPtr(ptr);
}

unsafe void Bad() {
     //ptr is now an invalid pointer, using it in any way will have
     //unpredictable results. This is exactly the same as accessing beyond
     //the bounds of the pointer.
     var ptr = Leak();
}

stackalloc może być używany tylko podczas deklarowania i inicjalizacji zmiennych. Poniższa nie jest poprawny:

byte* ptr;
...
ptr = stackalloc byte[1024];

Uwagi:

stackalloc powinien być używany tylko do optymalizacji wydajności (do obliczeń lub interakcji). Wynika to z faktu, że:

  • Garbage collector nie jest wymagany, ponieważ pamięć jest przydzielana na stosie, a nie na stosie - pamięć jest zwalniana, gdy tylko zmienna wykracza poza zakres
  • Przydzielanie pamięci na stosie jest szybsze niż na stercie
  • Zwiększ prawdopodobieństwo trafień w pamięć podręczną procesora ze względu na lokalizację danych

lotny

Dodanie volatile słowa kluczowego do pola wskazuje kompilatorowi, że wartość pola może zostać zmieniona przez wiele osobnych wątków. Podstawowym celem volatile słowa kluczowego jest zapobieganie optymalizacjom kompilatora, które zakładają dostęp tylko do jednowątkowego. Zastosowanie zmiennej volatile zapewnia, że wartość pola jest najnowszą dostępną wartością, a wartość nie podlega buforowaniu, jaką są wartości nietrwałe.

Dobrą praktyką jest oznaczanie każdej zmiennej, która może być używana przez wiele wątków, jako volatile aby zapobiec nieoczekiwanemu zachowaniu z powodu optymalizacji za kulisami. Rozważ następujący blok kodu:

public class Example
{
    public int x;

    public void DoStuff()
    {
        x = 5;

        // the compiler will optimize this to y = 15
        var y = x + 10;

        /* the value of x will always be the current value, but y will always be "15" */
        Debug.WriteLine("x = " + x + ", y = " + y);
    }    
}

W powyższym bloku kodu kompilator odczytuje instrukcje x = 5 i y = x + 10 i określa, że wartość y zawsze kończy się na 15. W ten sposób zoptymalizuje ostatnią instrukcję jako y = 15 . Jednak zmienna x jest w rzeczywistości polem public a wartość x może być modyfikowana w czasie wykonywania przez inny wątek działający osobno na tym polu. Teraz rozważ ten zmodyfikowany blok kodu. Zauważ, że pole x jest teraz zadeklarowane jako volatile .

public class Example
{
    public volatile int x;

    public void DoStuff()
    {
        x = 5;

        // the compiler no longer optimizes this statement
        var y = x + 10;

        /* the value of x and y will always be the correct values */
        Debug.WriteLine("x = " + x + ", y = " + y);
    }    
}

Teraz kompilator szuka użycia odczytu pola x i zapewnia, że bieżąca wartość pola jest zawsze pobierana. Zapewnia to, że nawet jeśli wiele wątków odczytuje i zapisuje w tym polu, zawsze pobierana jest bieżąca wartość x .

volatile może być używana tylko na polach wewnątrz class lub struct . Poniższa nie jest poprawny :

public void MyMethod()
{
    volatile int x;
}

volatile można zastosować tylko do pól następujących typów:

  • typy odniesienia lub ogólne parametry typu, o których wiadomo, że są typami odniesienia
  • prymitywne typy, takie jak sbyte , byte , short , ushort , int , uint , char , float i bool
  • typy sbyte na podstawie byte , sbyte , short , ushort , int lub uint
  • IntPtr i UIntPtr

Uwagi:

  • volatile modyfikator jest zwykle używany w polu, do którego dostęp ma wiele wątków, bez użycia instrukcji lock do szeregowania dostępu.
  • volatile słowo kluczowe można zastosować do pól typów referencyjnych
  • volatile słowo kluczowe nie spowoduje działania na 64-bitowych operacjach podstawowych na 32-bitowej platformie atomowej. Zablokowane operacje, takie jak Interlocked.Read i Interlocked.Exchange muszą być nadal używane do bezpiecznego, wielowątkowego dostępu na tych platformach.

naprawiony

Naprawiona instrukcja naprawia pamięć w jednym miejscu. Obiekty w pamięci zwykle poruszają się wokół, co umożliwia zbieranie śmieci. Ale kiedy używamy niebezpiecznych wskaźników do adresów pamięci, pamięci tej nie wolno przenosić.

  • Używamy stałej instrukcji, aby upewnić się, że śmieciarz nie przenosi danych ciągu.

Naprawiono zmienne

var myStr = "Hello world!";

fixed (char* ptr = myStr)
{
    // myStr is now fixed (won't be [re]moved by the Garbage Collector).
    // We can now do something with ptr.
}

Używany w niebezpiecznym kontekście.

Naprawiono rozmiar tablicy

unsafe struct Example
{
    public fixed byte SomeField[8];
    public fixed char AnotherField[64];
}

fixed może być użyty tylko w polach w struct (musi być również użyty w niebezpiecznym kontekście).

domyślna

W przypadku klas, interfejsów, delegata, tablicy, wartości zerowych (takich jak int?) I typów wskaźników default(TheType) zwraca null :

class MyClass {}
Debug.Assert(default(MyClass) == null);
Debug.Assert(default(string) == null);

W przypadku struktur i default(TheType) wartość default(TheType) zwraca to samo, co new TheType() :

struct Coordinates
{
    public int X { get; set; }
    public int Y { get; set; }
}

struct MyStruct
{
    public string Name { get; set; }
    public Coordinates Location { get; set; }
    public Coordinates? SecondLocation { get; set; }
    public TimeSpan Duration { get; set; }
}

var defaultStruct = default(MyStruct);
Debug.Assert(defaultStruct.Equals(new MyStruct()));
Debug.Assert(defaultStruct.Location.Equals(new Coordinates()));
Debug.Assert(defaultStruct.Location.X == 0);
Debug.Assert(defaultStruct.Location.Y == 0);
Debug.Assert(defaultStruct.SecondLocation == null);
Debug.Assert(defaultStruct.Name == null);
Debug.Assert(defaultStruct.Duration == TimeSpan.Zero);

default(T) może być szczególnie przydatna, gdy T jest parametrem ogólnym, dla którego nie ma ograniczenia pozwalającego zdecydować, czy T jest typem odniesienia czy typem wartości, na przykład:

public T GetResourceOrDefault<T>(string resourceName)
{
   if (ResourceExists(resourceName))
   {
      return (T)GetResource(resourceName);
   }
   else
   {
      return default(T);
   }
}

tylko czytać

readonly kluczowe readonly jest modyfikatorem pola. Gdy deklaracja pola zawiera modyfikator readonly do readonly , przypisania do tego pola mogą występować tylko jako część deklaracji lub w konstruktorze tej samej klasy.

readonly kluczowe readonly różni się od słowa kluczowego const . Pole const można zainicjować tylko w deklaracji pola. Pole readonly można zainicjować w deklaracji lub w konstruktorze. Dlatego pola readonly mogą mieć różne wartości w zależności od zastosowanego konstruktora.

readonly kluczowe readonly jest często używane podczas wstrzykiwania zależności.

class Person
{
    readonly string _name;
    readonly string _surname = "Surname";

    Person(string name)
    {
        _name = name;
    }
    void ChangeName()
    {
        _name = "another name"; // Compile error
        _surname = "another surname"; // Compile error
    }
}

Uwaga: Deklaracja pola tylko do odczytu nie oznacza niezmienności . Jeśli pole jest typem odniesienia, zawartość obiektu można zmienić. Tylko do odczytu jest zwykle używane, aby zapobiec nadpisaniu i przypisaniu obiektu tylko podczas tworzenia tego obiektu.

Uwaga: Wewnątrz konstruktora można ponownie przypisać pole tylko do odczytu

public class Car
{
    public double Speed {get; set;}
}

//In code

private readonly Car car = new Car();

private void SomeMethod()
{
    car.Speed = 100;
}

tak jak

Słowo kluczowe as jest operatorem podobnym do obsady . Jeśli rzutowanie nie jest możliwe, użycie as powoduje null zamiast skutkowania InvalidCastException .

expression as type jest równoważne expression is type ? (type)expression : (type)null z zastrzeżeniem, że as jest poprawne tylko w przypadku konwersji referencyjnych, zerowalnych konwersji i konwersji bokserskich. Konwersje zdefiniowane przez użytkownika nie są obsługiwane; zamiast tego należy użyć zwykłego obsady.

Dla powyższego rozszerzenia kompilator generuje kod, dzięki czemu expression zostanie ocenione tylko raz i użyje pojedynczego sprawdzania typu dynamicznego (w przeciwieństwie do dwóch z powyższej próbki).

as może być przydatne, gdy oczekuje się argumentu ułatwiającego kilka typów. Konkretnie przyznaje opcje wielu użytkowników - zamiast sprawdzać każdą możliwość ze is przed odlewaniem lub tylko rzucania i łapania wyjątków. Najlepszą praktyką jest używanie „jak” podczas rzucania / sprawdzania obiektu, co spowoduje tylko jedną karę za rozpakowanie. Użycie is na sprawdzeniu, a następnie rzutowanie spowoduje dwie kary za rozpakowanie.

Jeśli oczekuje się, że argument będzie instancją określonego typu, preferowana jest regularna obsada, ponieważ jego cel jest bardziej czytelny dla czytelnika.

Ponieważ wywołanie as może powodować null , zawsze sprawdź wynik, aby uniknąć NullReferenceException .

Przykładowe użycie

object something = "Hello";
Console.WriteLine(something as string);        //Hello
Console.Writeline(something as Nullable<int>); //null
Console.WriteLine(something as int?);          //null

//This does NOT compile:
//destination type must be a reference type (or a nullable value type)
Console.WriteLine(something as int);

Wersja demonstracyjna na żywo .NET Fiddle

Równoważny przykład bez użycia as :

Console.WriteLine(something is string ? (string)something : (string)null);

Jest to przydatne, gdy Equals funkcję Equals w klasach niestandardowych.

class MyCustomClass
{

    public override bool Equals(object obj)
    {
        MyCustomClass customObject = obj as MyCustomClass;

        // if it is null it may be really null
        // or it may be of a different type
        if (Object.ReferenceEquals(null, customObject))
        {
            // If it is null then it is not equal to this instance.
            return false;
        }

        // Other equality controls specific to class
    }

}

jest

Sprawdza, czy obiekt jest zgodny z danym typem, tj. Czy obiekt jest instancją typu BaseInterface lub typem pochodzącym z BaseInterface :

interface BaseInterface {}
class BaseClass : BaseInterface {}
class DerivedClass : BaseClass {}

var d = new DerivedClass();
Console.WriteLine(d is DerivedClass);  // True
Console.WriteLine(d is BaseClass);     // True
Console.WriteLine(d is BaseInterface); // True
Console.WriteLine(d is object);        // True
Console.WriteLine(d is string);        // False

var b = new BaseClass();
Console.WriteLine(b is DerivedClass);  // False
Console.WriteLine(b is BaseClass);     // True
Console.WriteLine(b is BaseInterface); // True
Console.WriteLine(b is object);        // True
Console.WriteLine(b is string);        // False

Jeśli intencją obsady jest użycie obiektu, najlepiej jest używać as słowa kluczowego”

interface BaseInterface {}
class BaseClass : BaseInterface {}
class DerivedClass : BaseClass {}

var d = new DerivedClass();
Console.WriteLine(d is DerivedClass);  // True - valid use of 'is'
Console.WriteLine(d is BaseClass);     // True - valid use of 'is'

if(d is BaseClass){
    var castedD = (BaseClass)d;
    castedD.Method(); // valid, but not best practice
}

var asD = d as BaseClass;

if(asD!=null){
    asD.Method(); //prefered method since you incur only one unboxing penalty
}

Ale od C # 7 funkcja pattern matching rozszerza operator is do sprawdzania typu i deklarowania nowej zmiennej w tym samym czasie. Ta sama część kodu z C # 7:

7.0
if(d is BaseClass asD ){
    asD.Method();
}

typ

Zwraca Type obiektu bez potrzeby jego tworzenia.

Type type = typeof(string);
Console.WriteLine(type.FullName); //System.String
Console.WriteLine("Hello".GetType() == type); //True
Console.WriteLine("Hello".GetType() == typeof(string)); //True

const

const służy do reprezentowania wartości, które nigdy się nie zmienią przez cały czas trwania programu. Jego wartość jest stała od czasu kompilacji , w przeciwieństwie do słowa kluczowego readonly do readonly , którego wartość jest stała od czasu wykonywania.

Na przykład, ponieważ prędkość światła nigdy się nie zmieni, możemy przechowywać ją w stałej.

const double c = 299792458;  // Speed of light

double CalculateEnergy(double mass)
{
    return mass * c * c;
}

Jest to w zasadzie to samo co posiadanie return mass * 299792458 * 299792458 , ponieważ kompilator bezpośrednio zastąpi c swoją stałą wartością.

W rezultacie c nie może zostać zmienione po zadeklarowaniu. Poniższe spowoduje błąd czasu kompilacji:

const double c = 299792458;  // Speed of light 

c = 500;  //compile-time error

Stała może być poprzedzona tymi samymi modyfikatorami dostępu co metody:

private const double c = 299792458;
public const double c = 299792458;
internal const double c = 299792458;

const elementy są z natury static . Jednak jawne używanie static jest niedozwolone.

Możesz także zdefiniować stałe lokalne dla metody:

double CalculateEnergy(double mass)
{
    const c = 299792458;
    return mass * c * c;
}

Nie mogą być poprzedzone private lub public słowem kluczowym, ponieważ są domyślnie lokalne dla metody, w której są zdefiniowane.


Nie wszystkie typy mogą być użyte w deklaracji const . Dozwolone typy wartości to predefiniowane typy sbyte , byte , short , ushort , int , uint , long , ulong , char , float , double , decimal , bool i wszystkie typy enum . Próba zadeklarowania const elementów z innymi typami wartości (takimi jak TimeSpan lub Guid ) zakończy się niepowodzeniem w czasie kompilacji.

W przypadku specjalnego wstępnie zdefiniowanego string typu odniesienia stałe mogą być deklarowane z dowolną wartością. Dla wszystkich innych typów odwołań stałe mogą być deklarowane, ale zawsze muszą mieć wartość null .


Ponieważ wartości const są znane w czasie kompilacji, są dozwolone jako etykiety case w instrukcji switch , jako standardowe argumenty dla parametrów opcjonalnych, jako argumenty specyfikacji atrybutów i tak dalej.


Jeśli wartości const są używane w różnych zestawach, należy zachować ostrożność przy wersjonowaniu. Na przykład, jeśli zestaw A definiuje public const int MaxRetries = 3; , a zestaw B używa tej stałej, to jeśli następnie wartość MaxRetries zostanie później zmieniona na 5 w zestawie A (który następnie zostanie ponownie skompilowany), zmiana ta nie będzie skuteczna w zestawie B, chyba że zestaw B również zostanie ponownie skompilowany (z odniesienie do nowej wersji A).

Z tego powodu, jeśli wartość może ulec zmianie w przyszłych wersjach programu i jeśli wartość musi być publicznie widoczna, nie deklaruj tej const wartości, chyba że wiesz, że wszystkie zależne zespoły zostaną ponownie skompilowane za każdym razem, gdy coś się zmieni. Alternatywą jest użycie static readonly zamiast const , który rozdziela się na starcie.

przestrzeń nazw

namespace kluczowe namespace to konstrukcja organizacyjna, która pomaga nam zrozumieć, w jaki sposób ułożona jest baza kodu. Przestrzenie nazw w języku C # to przestrzenie wirtualne, a nie znajdujące się w folderze fizycznym.

namespace StackOverflow
{
    namespace Documentation
    {
        namespace CSharp.Keywords
        {
            public class Program
            {
                public static void Main()
                {
                    Console.WriteLine(typeof(Program).Namespace);
                    //StackOverflow.Documentation.CSharp.Keywords
                }
            }
        }
    }
}

Przestrzenie nazw w języku C # można również pisać w łańcuchowej składni. Poniższe jest równoważne z powyższym:

namespace StackOverflow.Documentation.CSharp.Keywords
{
    public class Program
    {
        public static void Main()
        {
            Console.WriteLine(typeof(Program).Namespace);
            //StackOverflow.Documentation.CSharp.Keywords
        }
    }
}

spróbuj złapać, wreszcie rzuć

try , catch , finally i throw pozwalają obsłużyć wyjątki w kodzie.

var processor = new InputProcessor();

// The code within the try block will be executed. If an exception occurs during execution of
// this code, execution will pass to the catch block corresponding to the exception type.
try 
{
    processor.Process(input);
}
// If a FormatException is thrown during the try block, then this catch block
// will be executed.
catch (FormatException ex)
{
    // Throw is a keyword that will manually throw an exception, triggering any catch block that is
    // waiting for that exception type. 
    throw new InvalidOperationException("Invalid input", ex);
}
// catch can be used to catch all or any specific exceptions. This catch block,
// with no type specified, catches any exception that hasn't already been caught
// in a prior catch block.
catch
{
    LogUnexpectedException(); 
    throw; // Re-throws the original exception.
}
// The finally block is executed after all try-catch blocks have been; either after the try has
// succeeded in running all commands or after all exceptions have been caught. 
finally
{
    processor.Dispose();
}

Uwaga: return kluczowe return może być użyte w bloku try , a blok w finally będzie nadal wykonywany (tuż przed zwróceniem). Na przykład:

try 
{
    connection.Open();
    return connection.Get(query);
} 
finally 
{
    connection.Close();
}

Instrukcja connection.Close() zostanie wykonana przed zwróceniem wyniku connection.Get(query)

kontyntynuj

Natychmiast przekaż kontrolę następnej iteracji otaczającej konstrukcji pętli (for, foreach, do, while):

for (var i = 0; i < 10; i++)
{
    if (i < 5)
    {
        continue;
    }
    Console.WriteLine(i);
}

Wynik:

5
6
7
8
9

Wersja demonstracyjna na żywo .NET Fiddle

var stuff = new [] {"a", "b", null, "c", "d"};

foreach (var s in stuff)
{
    if (s == null)
    {
        continue;
    }           
    Console.WriteLine(s);
}

Wynik:

za
b
do
re

Wersja demonstracyjna na żywo .NET Fiddle

ref, out

Słowa kluczowe ref i out powodują, że argument jest przekazywany przez odwołanie, a nie przez wartość. W przypadku typów wartości oznacza to, że wartość zmiennej może być zmieniona przez odbiorcę.

int x = 5;
ChangeX(ref x);
// The value of x could be different now

W przypadku typów odniesienia, przykładowo w zmiennej nie mogą być zmodyfikowane (tak jak w przypadku bez ref ), ale mogą być także zastąpione zupełnie:

Address a = new Address();
ChangeFieldInAddress(a);
// a will be the same instance as before, even if it is modified
CreateANewInstance(ref a);
// a could be an entirely new instance now

Główną różnicą między słowem kluczowym out i ref jest to, że ref wymaga zainicjowania zmiennej przez osobę dzwoniącą, a out przenosi tę odpowiedzialność na odbiorcę.

Aby użyć parametru out , zarówno definicja metody, jak i metoda wywołująca muszą jawnie używać słowa kluczowego out .

int number = 1;
Console.WriteLine("Before AddByRef: " + number); // number = 1
AddOneByRef(ref number);
Console.WriteLine("After AddByRef: " + number);  // number = 2
SetByOut(out number);
Console.WriteLine("After SetByOut: " + number);  // number = 34

void AddOneByRef(ref int value)
{
    value++;
}

void SetByOut(out int value)
{
    value = 34;
}

Wersja demonstracyjna na żywo .NET Fiddle

Dodaje się nie kompiluje, bo out parametry muszą mieć przypisaną wartość przed powrotem metod (to skompilować za pomocą ref zamiast):

void PrintByOut(out int value)
{
    Console.WriteLine("Hello!");
}

używając naszego słowa kluczowego jako Generic Modifier

out kluczowe out może być również użyte w ogólnych parametrach typu podczas definiowania ogólnych interfejsów i delegatów. W takim przypadku słowo kluczowe out określa, że parametr typu jest kowariantny.

Kowariancja umożliwia użycie typu bardziej pochodnego niż określony przez parametr ogólny. Pozwala to na niejawną konwersję klas implementujących interfejsy wariantów i niejawną konwersję typów delegowanych. Kowariancja i kontrawariancja są obsługiwane dla typów referencyjnych, ale nie są obsługiwane dla typów wartości. - MSDN

//if we have an interface like this
interface ICovariant<out R> { }

//and two variables like
ICovariant<Object> iobj = new Sample<Object>();
ICovariant<String> istr = new Sample<String>();

// then the following statement is valid
// without the out keyword this would have thrown error
iobj = istr; // implicit conversion occurs here

zaznaczone, niezaznaczone

checked i unchecked słowa kluczowe określają, w jaki sposób operacje radzą sobie z przepełnieniem matematycznym. „Przepełnienie” w kontekście checked i unchecked słów kluczowych ma miejsce, gdy operacja arytmetyczna na liczbach całkowitych powoduje, że wartość jest większa niż wielkość, którą może reprezentować docelowy typ danych.

Kiedy przepełnienie występuje w checked bloku (lub gdy kompilator jest ustawiony na globalne używanie sprawdzonej arytmetyki), zgłaszany jest wyjątek ostrzegający przed niepożądanym zachowaniem. Tymczasem w unchecked bloku przepełnienie jest ciche: nie są zgłaszane żadne wyjątki, a wartość po prostu zawinie się do przeciwnej granicy. Może to prowadzić do subtelnych, trudnych do znalezienia błędów.

Ponieważ większość operacji arytmetycznych jest wykonywana na wartościach, które nie są wystarczająco duże lub zbyt małe, aby mogły zostać przepełnione, przez większość czasu nie ma potrzeby jawnego definiowania bloku jako checked . Należy zachować ostrożność, wykonując arytmetykę na nieograniczonych danych wejściowych, które mogą powodować przepełnienie, na przykład podczas wykonywania arytmetyki w funkcjach rekurencyjnych lub podczas wprowadzania danych przez użytkownika.

Ani checked ani unchecked wpływają na operacje arytmetyczne na liczbach zmiennoprzecinkowych.

Gdy blok lub wyrażenie zostanie zadeklarowane jako unchecked , wszelkie operacje arytmetyczne wewnątrz niego mogą zostać przepełnione bez powodowania błędu. Przykładem, w którym pożądane jest takie zachowanie, byłoby obliczenie sumy kontrolnej, w której wartość może „zawinąć się” podczas obliczania:

byte Checksum(byte[] data) {
    byte result = 0;
    for (int i = 0; i < data.Length; i++) {
        result = unchecked(result + data[i]); // unchecked expression
    }
    return result;
}

Jednym z najczęstszych zastosowań unchecked jest implementacja niestandardowego zastąpienia dla object.GetHashCode() , rodzaju sumy kontrolnej. Możesz zobaczyć użycie słowa kluczowego w odpowiedziach na to pytanie: Jaki jest najlepszy algorytm dla przesłoniętego System.Object.GetHashCode? .

Gdy blok lub wyrażenie zostanie zadeklarowane jako checked , każda operacja arytmetyczna powodująca przepełnienie powoduje zgłoszenie OverflowException .

int SafeSum(int x, int y) {
    checked { // checked block
        return x + y; 
    }
}

Zarówno zaznaczone, jak i niezaznaczone mogą być w formie bloku i wyrażenia.

Zaznaczone i niezaznaczone bloki nie wpływają na wywoływane metody, tylko operatory wywoływane bezpośrednio w bieżącej metodzie. Na przykład Enum.ToObject() , Convert.ToInt32() i operatory zdefiniowane przez użytkownika nie mają wpływu na niestandardowe zaznaczone / niezaznaczone konteksty.

Uwaga : Domyślne domyślne zachowanie przepełnienia (zaznaczone kontra niezaznaczone) można zmienić we właściwościach projektu lub za pomocą przełącznika wiersza polecenia / selected [+ | -] . Często sprawdzane są operacje sprawdzane w przypadku kompilacji debugowania i niezaznaczone w przypadku kompilacji wersji. checked i unchecked słowa kluczowe byłyby wówczas używane tylko wtedy, gdy domyślne podejście nie ma zastosowania i potrzebujesz jawnego zachowania, aby zapewnić poprawność.

iść do

goto może być użyte do przeskoczenia do określonej linii w kodzie, określonej przez etykietę.

goto jako:

Etykieta:

void InfiniteHello()
{
    sayHello:
    Console.WriteLine("Hello!");
    goto sayHello;
}

Wersja demonstracyjna na żywo .NET Fiddle

Oświadczenie o sprawie:

enum Permissions { Read, Write };

switch (GetRequestedPermission())
{
    case Permissions.Read:
        GrantReadAccess();
        break;

    case Permissions.Write:
        GrantWriteAccess();
        goto case Permissions.Read; //People with write access also get read
}

Wersja demonstracyjna na żywo .NET Fiddle

Jest to szczególnie przydatne w wykonywaniu wielu zachowań w instrukcji switch, ponieważ C # nie obsługuje bloków spraw awaryjnych.

Wyjątek Ponów próbę

var exCount = 0;
retry:
try
{
    //Do work
}
catch (IOException)
{
    exCount++;
    if (exCount < 3)
    {
        Thread.Sleep(100);
        goto retry;
    }
    throw;
}

Wersja demonstracyjna na żywo .NET Fiddle

Podobnie jak w wielu językach, użycie słowa kluczowego goto jest odradzane, z wyjątkiem poniższych przypadków.

Prawidłowe zastosowania goto które dotyczą C #:

  • Przypadek awaryjny w instrukcji switch.

  • Przerwa na wielu poziomach. Zamiast tego można często używać LINQ, ale zwykle ma on gorszą wydajność.

  • Zwolnienie zasobów podczas pracy z nieopakowanymi obiektami niskiego poziomu. W języku C # obiekty niskiego poziomu powinny być zwykle pakowane w osobne klasy.

  • Maszyny stanów skończonych, na przykład parsery; używane wewnętrznie przez asynchroniczne generowane przez kompilator maszyny stanu.

wyliczanie

enum kluczowe enum informuje kompilator, że ta klasa dziedziczy od klasy abstrakcyjnej Enum , bez konieczności bezpośredniego dziedziczenia przez programistę. Enum jest potomkiem ValueType , który jest przeznaczony do użycia z odrębnym zestawem nazwanych stałych.

public enum DaysOfWeek
{
    Monday,
    Tuesday,
}

Możesz opcjonalnie określić określoną wartość dla każdego (lub niektórych):

public enum NotableYear
{
   EndOfWwI = 1918;
   EnfOfWwII = 1945,
}

W tym przykładzie pominąłem wartość 0, jest to zwykle zła praktyka. enum zawsze będzie miało wartość domyślną wytworzoną przez jawną konwersję (YourEnumType) 0 , gdzie YourEnumType jest zadeklarowanym typem enume . Bez zdefiniowanej wartości 0, enum nie będzie miało określonej wartości przy inicjacji.

Domyślnym rodzajem enum jest int , możesz zmienić typ bazowy na dowolny typ całkowy, w tym byte , sbyte , short , ushort , int , uint , long i ulong . Poniżej znajduje się wyliczenie z bazowym byte typu:

enum Days : byte
{
    Sunday = 0,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday
};

Pamiętaj też, że możesz przekonwertować na / z typu podstawowego po prostu za pomocą rzutowania:

int value = (int)NotableYear.EndOfWwI;

Z tych powodów lepiej zawsze sprawdzaj, czy enum jest poprawne, gdy ujawniasz funkcje biblioteki:

void PrintNotes(NotableYear year)
{
    if (!Enum.IsDefined(typeof(NotableYear), year))
        throw InvalidEnumArgumentException("year", (int)year, typeof(NotableYear));

    // ...
}

baza

base słowo kluczowe służy do uzyskiwania dostępu do członków z klasy podstawowej. Jest powszechnie używany do wywoływania podstawowych implementacji metod wirtualnych lub do określania, który konstruktor podstawowy powinien zostać wywołany.

Wybór konstruktora

public class Child : SomeBaseClass {
    public Child() : base("some string for the base class")
    {
    }
}

public class SomeBaseClass {
    public SomeBaseClass()
    {
        // new Child() will not call this constructor, as it does not have a parameter
    }
    public SomeBaseClass(string message)
    {
        // new Child() will use this base constructor because of the specified parameter in Child's constructor
        Console.WriteLine(message);
    }
}

Podstawowa implementacja metody wirtualnej

public override void SomeVirtualMethod() {
    // Do something, then call base implementation
    base.SomeVirtualMethod();
}

Możliwe jest użycie słowa kluczowego base w celu wywołania implementacji base z dowolnej metody. Wiąże to wywołanie metody bezpośrednio z implementacją podstawową, co oznacza, że nawet jeśli nowe klasy potomne przesłaniają metodę wirtualną, implementacja podstawowa będzie nadal wywoływana, dlatego należy zachować ostrożność.

public class Parent
{
    public virtual int VirtualMethod()
    {
        return 1;
    }
}

public class Child : Parent
{
    public override int VirtualMethod() {
        return 11;
    }

    public int NormalMethod()
    {
        return base.VirtualMethod();
    }

    public void CallMethods()
    {
        Assert.AreEqual(11, VirtualMethod());

        Assert.AreEqual(1, NormalMethod());
        Assert.AreEqual(1, base.VirtualMethod());
    }
}

public class GrandChild : Child
{
    public override int VirtualMethod()
    {
        return 21;
    }

    public void CallAgain()
    {
        Assert.AreEqual(21, VirtualMethod());
        Assert.AreEqual(11, base.VirtualMethod());

        // Notice that the call to NormalMethod below still returns the value
        // from the extreme base class even though the method has been overridden
        // in the child class.
        Assert.AreEqual(1, NormalMethod());
    }
}

dla każdego

foreach służy do iteracji elementów tablicy lub elementów w kolekcji, która implementuje IEnumerable ✝.

var lines = new string[] { 
    "Hello world!", 
    "How are you doing today?", 
    "Goodbye"
};

foreach (string line in lines)
{
    Console.WriteLine(line);
}

To wyjdzie

"Witaj świecie!"
"Jak się masz dzisiaj?"
"Do widzenia"

Wersja demonstracyjna na żywo .NET Fiddle

Można wyjść z foreach pętli w dowolnym momencie, korzystając z przerwy słowa lub przejść do następnej iteracji przy użyciu nadal kluczowe.

var numbers = new int[] {1, 2, 3, 4, 5, 6};

foreach (var number in numbers)
{
    // Skip if 2
    if (number == 2)
        continue;

    // Stop iteration if 5
    if (number == 5)
        break;

    Console.Write(number + ", ");
}

// Prints: 1, 3, 4, 

Wersja demonstracyjna na żywo .NET Fiddle

Zauważ, że kolejność iteracji jest gwarantowana tylko dla niektórych kolekcji, takich jak tablice i List , ale nie jest gwarantowana dla wielu innych kolekcji.


✝ Podczas gdy IEnumerable jest zwykle używany do wskazywania zbiorów wymiennych, foreach wymaga tylko, aby kolekcja publicznie ujawniła metodę object GetEnumerator() , która powinna zwrócić obiekt bool MoveNext() metodę bool MoveNext() i object Current { get; } właściwość.

parametry

params pozwala parametrowi metody na otrzymywanie zmiennej liczby argumentów, tj. zero, jeden lub wiele argumentów jest dozwolonych dla tego parametru.

static int AddAll(params int[] numbers)
{
    int total = 0;
    foreach (int number in numbers)
    {
        total += number;
    }
    
    return total;
}

Ta metoda może być teraz wywoływana z typową listą argumentów int lub tablicą ints.

AddAll(5, 10, 15, 20);                // 50
AddAll(new int[] { 5, 10, 15, 20 });  // 50

params muszą pojawić się co najwyżej raz, a jeśli zostaną użyte, muszą być ostatnie na liście argumentów, nawet jeśli kolejny typ różni się od typu tablicy.


Zachowaj ostrożność podczas przeciążania funkcji podczas używania słowa kluczowego params . C # preferuje bardziej szczegółowe dopasowanie przeciążeń przed uciekania się do próby użycia przeciążeń z params . Na przykład, jeśli masz dwie metody:

static double Add(params double[] numbers)
{
    Console.WriteLine("Add with array of doubles");
    double total = 0.0;
    foreach (double number in numbers)
    {
        total += number;
    }
    
    return total;
}

static int Add(int a, int b)
{
    Console.WriteLine("Add with 2 ints");
    return a + b;
}

Następnie przeciążenie argumentem specyficzny 2 ma pierwszeństwo przed próbą params przeciążenie.

Add(2, 3);      //prints "Add with 2 ints"
Add(2, 3.0);    //prints "Add with array of doubles" (doubles are not ints)
Add(2, 3, 4);   //prints "Add with array of doubles" (no 3 argument overload)

przerwa

W pętli (for, foreach, do, while) instrukcja break przerywa wykonywanie najbardziej wewnętrznej pętli i wraca do kodu po niej. Można go również stosować z yield w której określa, że iterator dobiegł końca.

for (var i = 0; i < 10; i++)
{
    if (i == 5)
    {
        break;
    }
    Console.WriteLine("This will appear only 5 times, as the break will stop the loop.");
}

Wersja demonstracyjna na żywo .NET Fiddle

foreach (var stuff in stuffCollection)
{
    if (stuff.SomeStringProp == null)
        break;
    // If stuff.SomeStringProp for any "stuff" is null, the loop is aborted.
    Console.WriteLine(stuff.SomeStringProp);
}

Instrukcja break jest także używana w konstrukcjach przełączających, aby wyłamać przypadek lub segment domyślny.

switch(a)
{
    case 5:
        Console.WriteLine("a was 5!");
        break;

    default:
        Console.WriteLine("a was something else!");
        break;
}

W instrukcjach switch słowo kluczowe „break” jest wymagane na końcu każdej instrukcji case. Jest to sprzeczne z niektórymi językami, które pozwalają na „przewrócenie się” do następnej instrukcji przypadku w serii. Obejścia tego mogą obejmować instrukcje „goto” lub sekwencyjne układanie instrukcji „case”.

Poniższy kod da cyfry 0, 1, 2, ..., 9 i ostatni wiersz nie zostanie wykonany. yield break oznacza koniec funkcji (nie tylko pętlę).

public static IEnumerable<int> GetNumbers()
{
    int i = 0;
    while (true) {
        if (i < 10) {
            yield return i++;
        } else {
            yield break;
        }
    }
    Console.WriteLine("This line will not be executed");
}

Wersja demonstracyjna na żywo .NET Fiddle

Zauważ, że w przeciwieństwie do niektórych innych języków, nie ma sposobu na oznaczenie określonej przerwy w C #. Oznacza to, że w przypadku zagnieżdżonych pętli zatrzymana zostanie tylko najbardziej wewnętrzna pętla:

foreach (var outerItem in outerList)
{
    foreach (var innerItem in innerList)
    {
        if (innerItem.ShoudBreakForWhateverReason)
            // This will only break out of the inner loop, the outer will continue:
            break; 
    }
}

Jeśli chcesz wyjść z zewnętrznej pętli, możesz użyć jednej z kilku różnych strategii, takich jak:

  • Instrukcja goto , aby wyskoczyć z całej pętli.
  • Specyficzna zmienna flagi ( shouldBreak w poniższym przykładzie powinna być shouldBreak ), którą można sprawdzić na końcu każdej iteracji zewnętrznej pętli.
  • Refaktoryzacja kodu w celu użycia instrukcji return w najbardziej wewnętrznej części pętli lub uniknięcie całej struktury zagnieżdżonej pętli.
bool shouldBreak = false;
while(comeCondition)
{
    while(otherCondition)
    {
        if (conditionToBreak)
        {
            // Either tranfer control flow to the label below...
            goto endAllLooping;

            // OR use a flag, which can be checked in the outer loop:
            shouldBreak = true;
        }
    }

    if(shouldBreakNow)
    {
        break; // Break out of outer loop if flag was set to true
    }
}

endAllLooping: // label from where control flow will continue

abstrakcyjny

Nie można utworzyć instancji klasy oznaczonej słowem kluczowym abstract .

Klasa musi zostać oznaczona jako abstrakcyjna, jeśli zawiera elementy abstrakcyjne lub dziedziczy elementy abstrakcyjne, których nie implementuje. Klasa może być oznaczona jako abstrakcyjna, nawet jeśli nie są w nią zaangażowani członkowie abstrakcyjni.

Klasy abstrakcyjne są zwykle używane jako klasy podstawowe, gdy część implementacji musi zostać określona przez inny komponent.

abstract class Animal 
{
    string Name { get; set; }
    public abstract void MakeSound();
}

public class Cat : Animal 
{
    public override void MakeSound()
    {
        Console.WriteLine("Meov meov");
    }
}

public class Dog : Animal 
{   
    public override void MakeSound()
    {
        Console.WriteLine("Bark bark");
    }
}

Animal cat = new Cat();       // Allowed due to Cat deriving from Animal
cat.MakeSound();              // will print out "Meov meov"    

Animal dog = new Dog();       // Allowed due to Dog deriving from Animal
dog.MakeSound();              // will print out "Bark bark"

Animal animal = new Animal(); // Not allowed due to being an abstract class

Metoda, właściwość lub zdarzenie oznaczone abstract słowa kluczowego wskazuje, że oczekuje się, że implementacja dla tego elementu członkowskiego zostanie zapewniona w podklasie. Jak wspomniano powyżej, członkowie abstrakcyjni mogą pojawiać się tylko w klasach abstrakcyjnych.

abstract class Animal 
{
   public abstract string Name { get; set; }
}

public class Cat : Animal 
{
    public override string Name { get; set; }
}

public class Dog : Animal 
{
    public override string Name { get; set; }
}

zmiennoprzecinkowy, podwójny, dziesiętny

pływak

float to alias typu .NET System.Single . Umożliwia przechowywanie liczb zmiennoprzecinkowych pojedynczej precyzji IEEE 754. Ten typ danych występuje w mscorlib.dll którego domyślnie odwołuje się każdy projekt C # podczas ich tworzenia.

Przybliżony zakres: -3,4 × 10 38 do 3,4 × 10 38

Dokładność dziesiętna: 6-9 cyfr znaczących

Notacja :

float f = 0.1259;
var f1 = 0.7895f; // f is literal suffix to represent float values 

Należy zauważyć, że typ float często powoduje znaczne błędy zaokrąglania. W aplikacjach, w których ważna jest precyzja, należy rozważyć inne typy danych.


podwójnie

double jest aliasem dla typu danych .NET System.Double . Reprezentuje 64-bitową liczbę zmiennoprzecinkową o podwójnej precyzji. Ten typ danych jest obecny w mscorlib.dll który jest domyślnie mscorlib.dll w dowolnym projekcie w języku C #.

Zakres: ± 5,0 × 10 −324 do ± 1,7 × 10 308

Dokładność dziesiętna: 15-16 cyfr znaczących

Notacja :

double distance = 200.34; // a double value
double salary = 245; // an integer implicitly type-casted to double value
var marks = 123.764D; // D is literal suffix to represent double values

dziesiętny

decimal jest aliasem do typu danych .NET System.Decimal . Reprezentuje słowo kluczowe oznacza 128-bitowy typ danych. W porównaniu do typów zmiennoprzecinkowych, typ dziesiętny ma większą precyzję i mniejszy zakres, co czyni go odpowiednim do obliczeń finansowych i pieniężnych. Ten typ danych jest obecny w mscorlib.dll który jest domyślnie mscorlib.dll w dowolnym projekcie w języku C #.

Zakres: -7,9 × 10 28 do 7,9 × 10 28

Dokładność dziesiętna: 28–29 cyfr znaczących

Notacja :

decimal payable = 152.25m; // a decimal value
var marks = 754.24m; // m is literal suffix to represent decimal values

uint

Bez znaku liczba całkowita lub uint to liczbowy typ danych, który może przechowywać tylko dodatnie liczby całkowite. Jak sama nazwa wskazuje, reprezentuje 32-bitową liczbę całkowitą bez znaku. Samo słowo kluczowe uint jest aliasem dla typu Common Type System System.UInt32 . Ten typ danych jest obecny w mscorlib.dll , do którego domyślnie odwołuje się każdy projekt C # podczas ich tworzenia. Zajmuje cztery bajty miejsca w pamięci.

Liczba całkowita bez znaku może zawierać dowolną wartość od 0 do 4 294 967 295.

Przykłady, jak i teraz nie deklarować liczb całkowitych bez znaku

uint i = 425697; // Valid expression, explicitly stated to compiler
var i1 = 789247U; // Valid expression, suffix allows compiler to determine datatype
uint x = 3.0; // Error, there is no implicit conversion

Uwaga: Według Microsoft zaleca się stosowanie typu danych int wszędzie tam, gdzie to możliwe, ponieważ typ danych uint nie jest zgodny z CLS.

to

this kluczowe odnosi się do obecnego przykładu klasy (obiektu). W ten sposób można rozróżnić dwie zmienne o tej samej nazwie, jedną na poziomie klasy (pole) i jedną będącą parametrem (lub zmienną lokalną) metody.

public MyClass {
    int a;

    void set_a(int a)
    {
        //this.a refers to the variable defined outside of the method,
        //while a refers to the passed parameter.
        this.a = a;
    }
}

Inne zastosowania tego słowa kluczowego wiążą się z niestabilnymi obciążeniami konstruktora :

public MyClass(int arg) : this(arg, null)
{
}

i pisanie indeksatorów :

public string this[int idx1, string idx2]
{
    get { /* ... */ }
    set { /* ... */ }
}

i deklarując metody rozszerzenia :

public static int Count<TItem>(this IEnumerable<TItem> source)
{
    // ...
}

Jeśli nie ma konfliktu z lokalną zmienną lub parametrem, kwestią stylu jest, czy użyć this czy nie, więc this.MemberOfType i MemberOfType będą w tym przypadku równoważne. Zobacz także base słowo kluczowe.

Należy pamiętać, że jeżeli metoda rozszerzenie ma być nazywane na bieżącej instancji, this jest wymagane. Na przykład, jeśli jesteś w metodzie niestatycznej klasy, która implementuje IEnumerable<> i chcesz wcześniej wywołać rozszerzenie Count , musisz użyć:

this.Count()  // works like StaticClassForExtensionMethod.Count(this)

i this nie może być tam pominięte.

dla

Składnia: for (initializer; condition; iterator)

  • Pętla for jest powszechnie używana, gdy znana jest liczba iteracji.
  • Instrukcje w sekcji initializer uruchamiane tylko raz, przed przejściem do pętli.
  • Sekcja condition zawiera wyrażenie boolowskie, które jest oceniane na końcu każdej iteracji pętli w celu ustalenia, czy pętla powinna wyjść, czy powinna działać ponownie.
  • Sekcja iterator określa, co dzieje się po każdej iteracji korpusu pętli.

Ten przykład pokazuje jak for może być używany do iteracji ciągu znaków ciąg znaków:

string str = "Hello";
for (int i = 0; i < str.Length; i++)
{
    Console.WriteLine(str[i]);                
}

Wynik:

H.
mi
l
l
o

Wersja demonstracyjna na żywo .NET Fiddle

Wszystkie wyrażenia definiujące instrukcję for są opcjonalne; na przykład do utworzenia nieskończonej pętli używana jest następująca instrukcja:

for( ; ; )
{
    // Your code here
}

Sekcja initializer może zawierać wiele zmiennych, o ile są one tego samego typu. Sekcja condition może składać się z dowolnego wyrażenia, które może być ocenione na bool . Sekcja iterator może wykonywać wiele akcji oddzielonych przecinkami:

string hello = "hello";
for (int i = 0, j = 1, k = 9; i < 3 && k > 0; i++, hello += i) {
    Console.WriteLine(hello);
}

Wynik:

Witaj
cześć 1
hello12

Wersja demonstracyjna na żywo .NET Fiddle

podczas

Operator while wykonuje iterację bloku kodu, dopóki zapytanie warunkowe nie będzie równe false lub kod zostanie przerwany goto , return , break lub throw .

Składnia słowa kluczowego while :

while ( warunek ) { blok kodu; }

Przykład:

int i = 0;
while (i++ < 5)
{
    Console.WriteLine("While is on loop number {0}.", i);
}

Wynik:

„Gdy jest w pętli numer 1.”
„Gdy jest w pętli numer 2.”
„Gdy jest w pętli nr 3.”
„Gdy jest w pętli numer 4.”
„Gdy jest w pętli numer 5”.

Wersja demonstracyjna na żywo .NET Fiddle

Pętla while jest kontrolowana przy wejściu , ponieważ warunek jest sprawdzany przed wykonaniem załączonego bloku kodu. Oznacza to, że pętla while nie wykona instrukcji, jeśli warunek jest fałszywy.

bool a = false;

while (a == true)
{
    Console.WriteLine("This will never be printed.");
}

Podanie warunku „ while bez zapewnienia, że w pewnym momencie stanie się ono fałszywe, spowoduje nieskończoną lub nieskończoną pętlę. W miarę możliwości należy tego unikać, jednak mogą zaistnieć wyjątkowe okoliczności, gdy będzie to potrzebne.

Możesz utworzyć taką pętlę w następujący sposób:

while (true)
{
//...
}

Zauważ, że kompilator C # przekształci pętle takie jak

while (true)
{
// ...
}

lub

for(;;)
{
// ...
}

w

{
:label
// ...
goto label;
}

Zauważ, że pętla while może mieć dowolny warunek, bez względu na to, jak skomplikowany, pod warunkiem, że ocenia (lub zwraca) wartość logiczną (bool). Może także zawierać funkcję, która zwraca wartość logiczną (ponieważ taka funkcja zwraca ten sam typ, co wyrażenie takie jak `a == x '). Na przykład,

while (AgriculturalService.MoreCornToPick(myFarm.GetAddress()))
{
    myFarm.PickCorn();
}

powrót

MSDN: Instrukcja return kończy wykonywanie metody, w której się pojawia, i zwraca kontrolę nad metodą wywołującą. Może również zwrócić wartość opcjonalną. Jeśli metoda jest typu void, instrukcję return można pominąć.

public int Sum(int valueA, int valueB)
{
    return valueA + valueB;
}


public void Terminate(bool terminateEarly)
{
    if (terminateEarly) return; // method returns to caller if true was passed in
    else Console.WriteLine("Not early"); // prints only if terminateEarly was false
}

w

Słowo kluczowe in ma trzy zastosowania:

a) Jako część składni w instrukcji foreach lub jako część składni w zapytaniu LINQ

foreach (var member in sequence)
{
    // ...
}

b) W kontekście ogólnych interfejsów i ogólnych typów delegatów oznacza sprzeczność dla danego parametru typu:

public interface IComparer<in T>
{
    // ...
}

c) W kontekście zapytania LINQ odnosi się do kolekcji, której dotyczy zapytanie

var query = from x in source select new { x.Name, x.ID, };

za pomocą

Istnieją dwa rodzaje using użycia słowa kluczowego, using statement i using directive :

  1. za pomocą instrukcji :

    using kluczowe using zapewnia, że obiekty implementujące interfejs IDisposable są odpowiednio usuwane po użyciu. Instrukcja użycia zawiera osobny temat

  2. za pomocą dyrektywy

    Dyrektywa using ma trzy zastosowania, patrz strona msdn dla dyrektywy using . Istnieje osobny temat dotyczący dyrektywy w sprawie używania .

zapieczętowany

Po zastosowaniu do klasy sealed modyfikator zapobiega dziedziczeniu po nim innych klas.

class A { }
sealed class B : A { }
class C : B { } //error : Cannot derive from the sealed class

Po zastosowaniu do metody virtual (lub właściwości wirtualnej) sealed modyfikator zapobiega zastąpieniu tej metody (właściwości) w klasach pochodnych.

public class A 
{
    public sealed override string ToString() // Virtual method inherited from class Object
    {
        return "Do not override me!";
    }
}

public class B: A 
{
    public override string ToString() // Compile time error
    { 
        return "An attempt to override"; 
    }
}

rozmiar

Służy do uzyskania wielkości w bajtach dla typu niezarządzanego

int byteSize = sizeof(byte) // 1
int sbyteSize = sizeof(sbyte) // 1
int shortSize = sizeof(short) // 2
int ushortSize = sizeof(ushort) // 2
int intSize = sizeof(int) // 4
int uintSize = sizeof(uint) // 4
int longSize = sizeof(long) // 8
int ulongSize = sizeof(ulong) // 8
int charSize = sizeof(char) // 2(Unicode)
int floatSize = sizeof(float) // 4
int doubleSize = sizeof(double) // 8
int decimalSize = sizeof(decimal) // 16
int boolSize = sizeof(bool) // 1

statyczny

Modyfikator static służy do deklarowania elementu statycznego, który nie musi być utworzony w celu uzyskania dostępu, ale zamiast tego jest dostępny po prostu poprzez jego nazwę, tj. DateTime.Now .

static może być używany z klasami, polami, metodami, właściwościami, operatorami, zdarzeniami i konstruktorami.

Chociaż instancja klasy zawiera osobną kopię wszystkich pól instancji klasy, istnieje tylko jedna kopia każdego pola statycznego.

class A
{
    static public int count = 0;

    public A()
    {
        count++;
    }
}

class Program
{
    static void Main(string[] args)
    {
        A a = new A();
        A b = new A();
        A c = new A();

        Console.WriteLine(A.count); // 3 
    }
}

count równa się całkowitej liczbie wystąpień klasy A

Modyfikatora statycznego można także użyć do zadeklarowania konstruktora statycznego dla klasy, do zainicjowania danych statycznych lub uruchomienia kodu, który należy wywołać tylko raz. Konstruktory statyczne są wywoływane przed odwołaniem się do klasy po raz pierwszy.

class A
{
    static public DateTime InitializationTime;

    // Static constructor
    static A()
    {
        InitializationTime = DateTime.Now;
        // Guaranteed to only run once
        Console.WriteLine(InitializationTime.ToString());
    }
}

static class jest oznaczona słowem kluczowym static i może być używana jako korzystny kontener dla zestawu metod, które działają na parametrach, ale niekoniecznie wymagają powiązania z instancją. Ze względu na static charakter klasy nie można jej utworzyć, ale może ona zawierać static constructor . Niektóre cechy static class obejmują:

  • Nie można odziedziczyć
  • Nie może dziedziczyć po niczym innym niż Object
  • Może zawierać konstruktor statyczny, ale nie konstruktor instancji
  • Może zawierać tylko elementy statyczne
  • Jest zapieczętowany

Kompilator jest również przyjazny i poinformuje programistę, czy w klasie istnieją elementy instancji. Przykładem może być klasa statyczna, która konwertuje pomiary z USA i Kanady:

static class ConversionHelper {
    private static double oneGallonPerLitreRate = 0.264172;

    public static double litreToGallonConversion(int litres) {
        return litres * oneGallonPerLitreRate;
    }
}

Gdy klasy są zadeklarowane jako statyczne:

public static class Functions
{
  public static int Double(int value)
  {
    return value + value;
  }
}

wszystkie funkcje, właściwości lub elementy należące do klasy również muszą zostać zadeklarowane jako statyczne. Nie można utworzyć instancji klasy. Zasadniczo klasa statyczna pozwala tworzyć pakiety funkcji, które są logicznie pogrupowane.

Ponieważ C # 6 static może być również stosowany wraz using członków statycznych przywozowych i metod. Można ich wtedy używać bez nazwy klasy.

Stary sposób, bez using static :

using System;

public class ConsoleApplication
{
    public static void Main()
    {
         Console.WriteLine("Hello World!"); //Writeline is method belonging to static class Console
    }

}

Przykład using static

using static System.Console;

public class ConsoleApplication
{
    public static void Main()
    {
         WriteLine("Hello World!"); //Writeline is method belonging to static class Console
    }

}

Wady

Chociaż klasy statyczne mogą być niezwykle przydatne, mają swoje własne zastrzeżenia:

  • Po wywołaniu klasy statycznej klasa jest ładowana do pamięci i nie może być uruchomiona przez moduł wyrzucania elementów bezużytecznych, dopóki AppDomain mieszcząca klasę statyczną nie zostanie rozładowana.

  • Klasa statyczna nie może zaimplementować interfejsu.

int

int to alias dla System.Int32 , który jest typem danych dla 32-bitowych liczb całkowitych ze znakiem. Ten typ danych można znaleźć w mscorlib.dll którego domyślnie odwołuje się każdy projekt C # podczas ich tworzenia.

Zakres: od -2 147 483 648 do 2147 483 647

int int1 = -10007;
var int2 = 2132012521;     

długo

Długie słowo kluczowe służy do reprezentowania 64-bitowych liczb całkowitych ze znakiem. Jest to alias typu danych System.Int64 występujący w mscorlib.dll , do którego domyślnie odwołuje się każdy projekt C # podczas ich tworzenia.

Każdą długą zmienną można zadeklarować zarówno jawnie, jak i niejawnie:

long long1 = 9223372036854775806;  // explicit declaration, long keyword used
var long2 = -9223372036854775806L; // implicit declaration, 'L' suffix used

Długa zmienna może zawierać dowolną wartość z -9,223,372,036,854,775,808 do 9 223,372,036,854,775,807 i może być użyteczna w sytuacjach, w których zmienna musi zawierać wartość przekraczającą granice tego, co inne zmienne (takie jak zmienna int ) może pomieścić.

ulong

Słowo kluczowe używane dla 64-bitowych liczb całkowitych bez znaku. Reprezentuje typ danych System.UInt64 znaleziony w mscorlib.dll którego domyślnie odwołuje się każdy projekt C # podczas ich tworzenia.

Zakres: od 0 do 18 446,744,073,709,551,615

ulong veryLargeInt = 18446744073609451315;
var anotherVeryLargeInt = 15446744063609451315UL;

dynamiczny

dynamic słowo kluczowe jest używane w przypadku dynamicznie wpisywanych obiektów . Obiekty zadeklarowane jako dynamic rezygnują z kontroli statycznych w czasie kompilacji, a zamiast tego są oceniane w czasie wykonywania.

using System;
using System.Dynamic;

dynamic info = new ExpandoObject();
info.Id = 123;
info.Another = 456;

Console.WriteLine(info.Another);
// 456

Console.WriteLine(info.DoesntExist);
// Throws RuntimeBinderException

W poniższym przykładzie zastosowano dynamic z biblioteką Json.NET firmy Newtonsoft, aby łatwo odczytać dane ze zdezrializowanego pliku JSON.

try
{
    string json = @"{ x : 10, y : ""ho""}";
    dynamic deserializedJson = JsonConvert.DeserializeObject(json);
    int x = deserializedJson.x;
    string y = deserializedJson.y;
    // int z = deserializedJson.z; // throws RuntimeBinderException
}
catch (RuntimeBinderException e)
{
    // This exception is thrown when a property
    // that wasn't assigned to a dynamic variable is used
}

Istnieje kilka ograniczeń związanych z dynamicznym słowem kluczowym. Jednym z nich jest użycie metod rozszerzenia. W poniższym przykładzie dodano metodę rozszerzenia ciągu: SayHello .

static class StringExtensions
{
    public static string SayHello(this string s) => $"Hello {s}!";
}

Pierwszym podejściem będzie wywołanie go jak zwykle (jak dla ciągu):

var person = "Person";
Console.WriteLine(person.SayHello());

dynamic manager = "Manager";
Console.WriteLine(manager.SayHello()); // RuntimeBinderException

Brak błędu kompilacji, ale w czasie wykonywania pojawia się RuntimeBinderException . Obejściem tego problemu będzie wywołanie metody rozszerzenia za pomocą klasy statycznej:

var helloManager = StringExtensions.SayHello(manager);
Console.WriteLine(helloManager);

wirtualny, zastąpienie, nowy

wirtualny i zastąpienie

virtual słowo kluczowe pozwala zastąpić metodę, właściwość, indeksatora lub zdarzenie klasami pochodnymi i przedstawić zachowanie polimorficzne. (Członkowie domyślnie nie są wirtualni w C #)

public class BaseClass
{
    public virtual void Foo()
    {
        Console.WriteLine("Foo from BaseClass");
    }
}

Aby zastąpić członka, słowo kluczowe override jest używane w klasach pochodnych. (Uwaga: podpis członków musi być identyczny)

public class DerivedClass: BaseClass
{
    public override void Foo()
    {
        Console.WriteLine("Foo from DerivedClass");
    }
}

Zachowanie polimorficzne wirtualnych elementów oznacza, że po wywołaniu rzeczywisty wykonywany element jest określany w czasie wykonywania, a nie w czasie kompilacji. Nadrzędny element w klasie najbardziej pochodnej, dla której dany obiekt jest instancją, zostanie wykonany.

Krótko mówiąc, obiekt można zadeklarować typu BaseClass w czasie kompilacji, ale jeśli w czasie wykonywania jest to instancja klasy DerivedClass wówczas nadpisany element zostanie wykonany:

BaseClass obj1 = new BaseClass();
obj1.Foo(); //Outputs "Foo from BaseClass"

obj1 = new DerivedClass();
obj1.Foo(); //Outputs "Foo from DerivedClass"    

Przesłonięcie metody jest opcjonalne:

public class SecondDerivedClass: DerivedClass {}

var obj1 = new SecondDerivedClass();
obj1.Foo(); //Outputs "Foo from DerivedClass"    

Nowy

Ponieważ tylko elementy zdefiniowane jako virtual są nadpisywane i polimorficzne, klasa pochodna przedefiniowująca element niebędący elementem wirtualnym może prowadzić do nieoczekiwanych wyników.

public class BaseClass
{
    public void Foo()
    {
        Console.WriteLine("Foo from BaseClass");
    }
}

public class DerivedClass: BaseClass
{
    public void Foo()
    {
        Console.WriteLine("Foo from DerivedClass");
    }
}

BaseClass obj1 = new BaseClass();
obj1.Foo(); //Outputs "Foo from BaseClass"

obj1 = new DerivedClass();
obj1.Foo(); //Outputs "Foo from BaseClass" too!    

Kiedy tak się dzieje, wykonywany element jest zawsze określany w czasie kompilacji na podstawie typu obiektu.

  • Jeśli obiekt jest zadeklarowany typu BaseClass (nawet jeśli w czasie wykonywania należy do klasy pochodnej), wówczas wykonywana jest metoda BaseClass
  • Jeśli obiekt jest zadeklarowany typu DerivedClass wówczas wykonywana jest metoda DerivedClass .

Zazwyczaj jest to wypadek (gdy element dodawany jest do typu podstawowego po dodaniu identycznego elementu do typu pochodnego) i w tych scenariuszach generowane jest ostrzeżenie kompilatora CS0108 .

Jeśli było to zamierzone, new słowo kluczowe służy do tłumienia ostrzeżenia kompilatora (i informuje innych programistów o twoich zamiarach!). zachowanie pozostaje takie samo, new słowo kluczowe po prostu tłumi ostrzeżenie kompilatora.

public class BaseClass
{
    public void Foo()
    {
        Console.WriteLine("Foo from BaseClass");
    }
}

public class DerivedClass: BaseClass
{
    public new void Foo()
    {
        Console.WriteLine("Foo from DerivedClass");
    }
}

BaseClass obj1 = new BaseClass();
obj1.Foo(); //Outputs "Foo from BaseClass"

obj1 = new DerivedClass();
obj1.Foo(); //Outputs "Foo from BaseClass" too! 

Zastosowanie zastąpienia nie jest opcjonalne

W przeciwieństwie do C ++ użycie słowa kluczowego override nie jest opcjonalne:

public class A
{
    public virtual void Foo()
    {
    }
}

public class B : A
{
    public void Foo() // Generates CS0108
    {
    }
}

Powyższy przykład powoduje również ostrzeżenie CS0108 , ponieważ B.Foo() nie zastępuje automatycznie A.Foo() . Dodaj override gdy zamiarem jest przesłonięcie klasy podstawowej i spowodowanie zachowania polimorficznego, dodaj new gdy chcesz zachować zachowanie niepolimorficzne i rozstrzygnij wywołanie za pomocą typu statycznego. Tego ostatniego należy używać ostrożnie, ponieważ może powodować poważne zamieszanie.

Poniższy kod powoduje nawet błąd:

public class A
{
    public void Foo()
    {
    }
}

public class B : A
{
    public override void Foo() // Error: Nothing to override
    {
    }
}

Klasy pochodne mogą wprowadzać polimorfizm

Poniższy kod jest całkowicie poprawny (choć rzadki):

    public class A
    {
        public void Foo()
        {
            Console.WriteLine("A");
        }
    }

    public class B : A
    {
        public new virtual void Foo() 
        {
            Console.WriteLine("B");
        }
    }

Teraz wszystkie obiekty ze statycznym odniesieniem B (i jego pochodnych) używają polimorfizmu do rozwiązania Foo() , podczas gdy odniesienia A używają A.Foo() .

A a = new A();
a.Foo(); // Prints "A";
a = new B();
a.Foo(); // Prints "A";
B b = new B();
b.Foo(); // Prints "B";

Metody wirtualne nie mogą być prywatne

Kompilator C # jest surowy w zapobieganiu bezsensownym konstrukcjom. Metody oznaczone jako virtual nie mogą być prywatne. Ponieważ prywatnej metody nie można zobaczyć z typu pochodnego, nie można jej również zastąpić. Nie można skompilować:

public class A
{
    private virtual void Foo() // Error: virtual methods cannot be private
    {
    }
}

asynchronizuj, czekaj

await kluczowe await zostało dodane w ramach wersji C # 5.0, która jest obsługiwana od Visual Studio 2012 roku. Wykorzystuje bibliotekę zadań równoległych (TPL), co znacznie ułatwiło wielowątkowość. Słowa kluczowe async i await są używane w parach w tej samej funkcji, jak pokazano poniżej. await kluczowe await służy do wstrzymania wykonywania bieżącej metody asynchronicznej, dopóki oczekiwane zadanie asynchroniczne nie zostanie zakończone i / lub nie zostaną zwrócone jego wyniki. Aby użyć słowa kluczowego await , metoda, która go używa, musi być oznaczona słowem kluczowym async .

Używanie async z void jest zdecydowanie odradzane. Aby uzyskać więcej informacji, możesz zajrzeć tutaj .

Przykład:

public async Task DoSomethingAsync()
{    
    Console.WriteLine("Starting a useless process...");
    Stopwatch stopwatch = Stopwatch.StartNew();
    int delay = await UselessProcessAsync(1000);
    stopwatch.Stop();
    Console.WriteLine("A useless process took {0} milliseconds to execute.", stopwatch.ElapsedMilliseconds);
}

public async Task<int> UselessProcessAsync(int x)
{
    await Task.Delay(x);
    return x;
}

Wynik:

„Rozpoczęcie bezużytecznego procesu ...”

** ... 1 sekundowe opóźnienie ... **

„Wykonanie bezużytecznego procesu zajęło 1000 milisekund”.

Pary słów kluczowych async i await można pominąć, jeśli metoda zwracająca Task lub Task<T> zwraca tylko jedną operację asynchroniczną.

Zamiast tego:

public async Task PrintAndDelayAsync(string message, int delay)
{
    Debug.WriteLine(message);
    await Task.Delay(x);
}

Zaleca się to zrobić:

public Task PrintAndDelayAsync(string message, int delay)
{
    Debug.WriteLine(message);
    return Task.Delay(x);
}
5.0

W C # 5.0 await nie może być użyte w catch i finally .

6.0

Z C # 6.0 można await na catch i finally .

zwęglać

Znak to pojedyncza litera przechowywana w zmiennej. Jest to wbudowany typ wartości, który zajmuje dwa bajty miejsca w pamięci. Reprezentuje typ danych System.Char znaleziony w mscorlib.dll którego domyślnie odwołuje się każdy projekt C # podczas ich tworzenia.

Można to zrobić na wiele sposobów.

  1. char c = 'c';
  2. char c = '\u0063'; //Unicode
  3. char c = '\x0063'; //Hex
  4. char c = (char)99;//Integral

Znak może być niejawnie konwertowany na ushort, int, uint, long, ulong, float, double, lub decimal i zwróci wartość całkowitą tego znaku.

ushort u = c;

zwraca 99 itd.

Jednak nie ma niejawnych konwersji z innych typów na char. Zamiast tego musisz je rzucić.

ushort u = 99;
 char c = (char)u;

zamek

lock zapewnia bezpieczeństwo wątku dla bloku kodu, dzięki czemu dostęp do niego może mieć tylko jeden wątek w ramach tego samego procesu. Przykład:

private static object _lockObj = new object();
static void Main(string[] args)
{
    Task.Run(() => TaskWork());
    Task.Run(() => TaskWork());
    Task.Run(() => TaskWork());

    Console.ReadKey();
}

private static void TaskWork()
{
    lock(_lockObj)
    {
        Console.WriteLine("Entered");

        Task.Delay(3000);
        Console.WriteLine("Done Delaying");

        // Access shared resources safely

        Console.WriteLine("Leaving");
    }   
}

Output:

Entered
Done Delaying
Leaving
Entered
Done Delaying
Leaving
Entered
Done Delaying
Leaving

Przypadków użycia:

Ilekroć masz blok kodu, który może wywoływać skutki uboczne, jeśli jest wykonywany przez wiele wątków jednocześnie. Aby temu zapobiec, można użyć słowa kluczowego lock wraz ze współdzielonym obiektem synchronizacji (w przykładzie _objLock ).

Zauważ, że _objLock nie może mieć null a wiele wątków wykonujących kod musi używać tej samej instancji obiektu (albo poprzez ustawienie pola static , albo przez użycie tej samej instancji klasy dla obu wątków)

Od strony kompilatora słowo kluczowe lock to cukier składniowy, który jest zastępowany przez Monitor.Enter(_lockObj); i Monitor.Exit(_lockObj); . Jeśli więc zastąpisz blokadę otaczając blok kodu tymi dwiema metodami, uzyskasz te same wyniki. Rzeczywisty kod można zobaczyć w cukrze syntaktycznym w C # - przykład blokady

zero

Zmienna typu odwołania może zawierać poprawne odwołanie do instancji lub odwołanie zerowe. Odwołanie zerowe jest wartością domyślną zmiennych typu odwołania, a także typów wartości dopuszczających wartości zerowe.

null to słowo kluczowe reprezentujące odwołanie null.

Jako wyrażenie można go użyć do przypisania pustego odwołania do zmiennych wyżej wymienionych typów:

object a = null;
string b = null;
int? c = null;
List<int> d  = null;

Typom wartości, które nie mają wartości dopuszczających wartości zerowe, nie można przypisać odwołania zerowego. Wszystkie następujące zadania są nieprawidłowe:

int a = null; 
float b = null;
decimal c = null;

Odwołania zerowego nie należy mylić z prawidłowymi instancjami różnych typów, takimi jak:

  • pusta lista ( new List<int>() )
  • pusty ciąg ( "" )
  • liczba zero ( 0 , 0f , 0m )
  • znak null ( '\0' )

Czasami warto sprawdzić, czy coś ma wartość null lub pusty / domyślny obiekt. Można to sprawdzić za pomocą metody System.String.IsNullOrEmpty (String) lub zaimplementować własną równoważną metodę.

private void GreetUser(string userName)
{
    if (String.IsNullOrEmpty(userName))
    {
        //The method that called us either sent in an empty string, or they sent us a null reference. Either way, we need to report the problem.
        throw new InvalidOperationException("userName may not be null or empty.");
    }
    else
    {
        //userName is acceptable.
        Console.WriteLine("Hello, " + userName + "!");
    }
}

wewnętrzny

internal słowo kluczowe jest modyfikatorem dostępu dla typów i członków typów. Typy wewnętrzne lub elementy są dostępne tylko w plikach w tym samym zestawie

stosowanie:

public class BaseClass 
{
    // Only accessible within the same assembly
    internal static int x = 0;
}

Różnica między różnymi modyfikatorami dostępu została wyjaśniona tutaj

Modyfikatory dostępu

publiczny

Dostęp do typu lub elementu można uzyskać za pomocą dowolnego kodu w tym samym zestawie lub innym zestawie, który się do niego odwołuje.

prywatny

Dostęp do typu lub elementu można uzyskać tylko za pomocą kodu w tej samej klasie lub strukturze.

chroniony

Dostęp do typu lub elementu można uzyskać tylko za pomocą kodu w tej samej klasie lub strukturze lub w klasie pochodnej.

wewnętrzny

Dostęp do typu lub elementu można uzyskać za pomocą dowolnego kodu w tym samym zestawie, ale nie z innego zestawu.

chronione wewnętrznie

Dostęp do typu lub elementu można uzyskać za pomocą dowolnego kodu w tym samym zestawie lub dowolnej klasy pochodnej w innym zestawie.

Jeśli nie ustawiono żadnego modyfikatora dostępu, używany jest domyślny modyfikator dostępu. Tak więc zawsze istnieje jakaś forma modyfikatora dostępu, nawet jeśli nie jest ustawiona.

gdzie

where może służyć do dwóch celów w języku C #: ograniczanie typu w ogólnym argumencie i filtrowanie zapytań LINQ.

Rozważmy w klasie ogólnej

public class Cup<T>
{
    // ...
}

T nazywa się parametrem typu. Definicja klasy może nakładać ograniczenia na rzeczywiste typy, które mogą być dostarczone dla T.

Można zastosować następujące rodzaje ograniczeń:

  • typ wartości
  • typ odniesienia
  • domyślny konstruktor
  • dziedziczenie i wdrożenie

typ wartości

W takim przypadku można podać tylko struct (w tym typy danych pierwotnych, takie jak int , boolean itp.)

public class Cup<T> where T : struct
{
    // ...
}

typ odniesienia

W takim przypadku można podać tylko typy klas

public class Cup<T> where T : class
{
    // ...
}

wartość hybrydowa / typ odniesienia

Czasami pożądane jest ograniczenie argumentów typu do argumentów dostępnych w bazie danych, które zwykle będą mapowane na typy wartości i ciągi znaków. Ponieważ wszystkie ograniczenia typu muszą być spełnione, nie można określić, where T : struct or string (nie jest to poprawna składnia). Obejściem tego problemu jest ograniczenie argumentów typu do IConvertible który ma wbudowane typy „... Boolean, SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double, Decimal, DateTime, Char i String. „ Możliwe, że inne obiekty zaimplementują IConvertible, choć w praktyce jest to rzadkie.

public class Cup<T> where T : IConvertible
{
    // ...
}

domyślny konstruktor

Dozwolone będą tylko typy zawierające domyślny konstruktor. Obejmuje to typy wartości i klasy, które zawierają domyślny (bez parametrów) konstruktor

public class Cup<T> where T : new
{
    // ...
}

dziedziczenie i wdrożenie

Dostarczone mogą być tylko typy, które dziedziczą po określonej klasie bazowej lub implementują określony interfejs.

public class Cup<T> where T : Beverage
{
    // ...
}


public class Cup<T> where T : IBeer
{
    // ...
}

Ograniczenie może nawet odwoływać się do innego parametru typu:

public class Cup<T, U> where U : T
{
    // ...
}

Dla argumentu typu można określić wiele ograniczeń:

public class Cup<T> where T : class, new()
{
    // ...
}

Poprzednie przykłady pokazują ogólne ograniczenia definicji klasy, ale ograniczeń można używać wszędzie tam, gdzie podawany jest argument typu: klasy, struktury, interfejsy, metody itp.

where może być również klauzula LINQ. W tym przypadku jest to analogiczne do WHERE w SQL:

int[] nums = { 5, 2, 1, 3, 9, 8, 6, 7, 2, 0 };

var query =
    from num in nums 
    where num < 5
    select num;

    foreach (var n in query)
    {
        Console.Write(n + " ");
    }
    // prints 2 1 3 2 0

zewnętrzny

extern kluczowe extern służy do deklarowania metod zaimplementowanych zewnętrznie. Można tego użyć w połączeniu z atrybutem DllImport w celu wywołania niezarządzanego kodu za pomocą usług Interop. który w tym przypadku będzie wyposażony w modyfikator static

Na przykład:

using System.Runtime.InteropServices;
public class MyClass
{
    [DllImport("User32.dll")]
    private static extern int SetForegroundWindow(IntPtr point);

    public void ActivateProcessWindow(Process p)
    {
        SetForegroundWindow(p.MainWindowHandle);
    }
}

Używa to metody SetForegroundWindow zaimportowanej z biblioteki User32.dll

Można to również wykorzystać do zdefiniowania aliasu zespołu zewnętrznego. które pozwalają nam odwoływać się do różnych wersji tych samych komponentów z jednego zestawu.

Aby odwoływać się do dwóch zestawów o tych samych w pełni kwalifikowanych nazwach typów, w wierszu polecenia należy podać alias w następujący sposób:

/r:GridV1=grid.dll
/r:GridV2=grid20.dll

To tworzy zewnętrzne aliasy GridV1 i GridV2. Aby użyć tych aliasów w programie, odwołaj się do nich za pomocą słowa kluczowego extern. Na przykład:

extern alias GridV1;
extern alias GridV2;

bool

Słowo kluczowe do przechowywania wartości logicznych true i false . bool to alias System.Boolean.

Domyślna wartość bool to false.

bool b; // default value is false
b = true; // true
b = ((5 + 2) == 6); // false

Aby bool dopuszczał wartości zerowe, musi być zainicjowany jako bool ?.

Domyślna wartość bool? jest zerowy.

bool? a // default value is null

gdy

when jest słowem kluczowym dodanym w C # 6 i służy do filtrowania wyjątków.

Przed wprowadzeniem słowa kluczowego when mogła istnieć jedna klauzula catch dla każdego rodzaju wyjątku; po dodaniu słowa kluczowego możliwa jest teraz bardziej szczegółowa kontrola.

when wyrażenie jest dołączone do gałęzi catch i tylko jeśli warunek when jest true , klauzula catch zostanie wykonana. Możliwe jest posiadanie kilku klauzul catch z tymi samymi typami klas wyjątków i różnymi, when warunki.

private void CatchException(Action action)
{
    try
    {
        action.Invoke();
    }
    
    // exception filter
    catch (Exception ex) when (ex.Message.Contains("when"))
    {
        Console.WriteLine("Caught an exception with when");
    }

    catch (Exception ex)
    {
        Console.WriteLine("Caught an exception without when");
    }
}

private void Method1() { throw new Exception("message for exception with when"); }
private void Method2() { throw new Exception("message for general exception"); }


CatchException(Method1);
CatchException(Method2);

niepowstrzymany

unchecked słowo kluczowe uniemożliwia kompilatorowi sprawdzenie przepełnienia / niedopełnienia.

Na przykład:

const int ConstantMax = int.MaxValue;
unchecked
{
    int1 = 2147483647 + 10;
}
int1 = unchecked(ConstantMax + 10);

Bez unchecked słowa kluczowego żadna z dwóch operacji dodawania nie zostanie skompilowana.

Kiedy to jest przydatne?

Jest to przydatne, ponieważ może przyspieszyć obliczenia, które na pewno się nie przepełnią, ponieważ sprawdzanie przepełnienia zajmuje dużo czasu lub gdy pożądane jest zachowanie przepełnienia / niedopełnienia (na przykład podczas generowania kodu skrótu).

unieważnić

Słowo zastrzeżone "void" jest aliasem typu System.Void i ma dwa zastosowania:

  1. Zadeklaruj metodę, która nie ma wartości zwracanej:
public void DoSomething()
{
    // Do some work, don't return any value to the caller.
}

Metoda z typem zwracanym void może nadal zawierać słowo kluczowe return w treści. Jest to przydatne, gdy chcesz wyjść z wykonywania metody i zwrócić przepływ wywołującemu:

public void DoSomething()
{
    // Do some work...

    if (condition)
        return;

    // Do some more work if the condition evaluated to false.
}
  1. Zadeklaruj wskaźnik do nieznanego typu w niebezpiecznym kontekście.

W niebezpiecznym kontekście typ może być typem wskaźnika, typem wartości lub typem odniesienia. Deklaracja typu wskaźnika jest zwykle type* identifier , gdzie typ jest znanym typem - tj. int* myInt , ale może być także void* identifier , w którym typ jest nieznany.

Pamiętaj, że firma Microsoft odradza deklarowanie typu pustego wskaźnika .

jeśli, jeśli ... jeszcze, jeśli ... jeszcze jeśli


Instrukcja if służy do kontrolowania przepływu programu. Instrukcja if określa, która instrukcja ma zostać uruchomiona na podstawie wartości wyrażenia Boolean .

W przypadku pojedynczej instrukcji braces {} są opcjonalne, ale zalecane.

int a = 4;
if(a % 2 == 0) 
{
     Console.WriteLine("a contains an even number");
}
// output: "a contains an even number"

if może również zawierać klauzulę else , która zostanie wykonana w przypadku, gdy warunek będzie miał wartość false:

int a = 5;
if(a % 2 == 0) 
{
     Console.WriteLine("a contains an even number");
}
else
{
     Console.WriteLine("a contains an odd number");
}
// output: "a contains an odd number"

Konstrukcja if ... else if umożliwia określenie wielu warunków:

int a = 9;
if(a % 2 == 0) 
{
     Console.WriteLine("a contains an even number");
}
else if(a % 3 == 0) 
{
     Console.WriteLine("a contains an odd number that is a multiple of 3"); 
}
else
{
     Console.WriteLine("a contains an odd number");
}
// output: "a contains an odd number that is a multiple of 3"

Ważne jest, aby pamiętać, że jeśli warunek jest spełniony w powyższym przykładzie, formant pomija inne testy i przeskakuje na koniec tego konkretnego, jeśli skonstruowano inaczej. Kolejność testów jest ważna, jeśli używasz skryptu if ... else if skonstruowany

Wyrażenia logiczne w języku C # używają oceny zwarcia . Jest to ważne w przypadkach, gdy ocena warunków może mieć skutki uboczne:

if (someBooleanMethodWithSideEffects() && someOtherBooleanMethodWithSideEffects()) {
  //...
}

Nie ma gwarancji, że someOtherBooleanMethodWithSideEffects faktycznie będą działać.

Jest to również ważne w przypadkach, gdy wcześniejsze warunki zapewniają, że „bezpieczne” jest ocenianie późniejszych. Na przykład:

if (someCollection != null && someCollection.Count > 0) {
   // ..
}

W tym przypadku kolejność jest bardzo ważna, ponieważ jeśli odwrócimy kolejność:

if (someCollection.Count > 0 && someCollection != null) {

zgłosi someCollection NullReferenceException jeśli someCollection ma null .

robić

Operator do wykonuje iterację bloku kodu, dopóki zapytanie warunkowe nie będzie równe false. Pętla do-while może być również przerwana przez goto , return , break lub throw .

Składnia do słowa kluczowego jest:

do { blok kodu; } while ( warunek );

Przykład:

int i = 0;

do
{
    Console.WriteLine("Do is on loop number {0}.", i);
} while (i++ < 5);

Wynik:

„Do jest w pętli nr 1”.
„Do jest w pętli nr 2.”
„Do jest w pętli nr 3.”
„Do jest w pętli numer 4.”
„Do jest w pętli nr 5”.

W przeciwieństwie do pętli while pętla do-while jest kontrolowana przez wyjście . Oznacza to, że pętla do-while wykona swoje instrukcje co najmniej raz, nawet jeśli warunek nie powiedzie się za pierwszym razem.

bool a = false;

do
{
    Console.WriteLine("This will be printed once, even if a is false.");
} while (a == true);

operator

Większość wbudowanych operatorów (w tym operatorów konwersji) może zostać przeciążona przy użyciu słowa kluczowego operator wraz z public i static modyfikatorami.

Operatory występują w trzech postaciach: operatory jednoargumentowe, operatory binarne i operatory konwersji.

Jednoargumentowe i binarne operatory wymagają co najmniej jednego parametru tego samego typu co typ zawierający, a niektóre wymagają uzupełniającego operatora dopasowywania.

Operatory konwersji muszą konwertować na lub z typu otaczającego.

public struct Vector32
{
    
    public Vector32(int x, int y)
    {
        X = x;
        Y = y;
    }
    
    public int X { get; }
    public int Y { get; }

    public static bool operator ==(Vector32 left, Vector32 right)
        => left.X == right.X && left.Y == right.Y;

    public static bool operator !=(Vector32 left, Vector32 right)
        => !(left == right);

    public static Vector32 operator +(Vector32 left, Vector32 right)
        => new Vector32(left.X + right.X, left.Y + right.Y);

    public static Vector32 operator +(Vector32 left, int right)
        => new Vector32(left.X + right, left.Y + right);

    public static Vector32 operator +(int left, Vector32 right)
        => right + left;

    public static Vector32 operator -(Vector32 left, Vector32 right)
        => new Vector32(left.X - right.X, left.Y - right.Y);

    public static Vector32 operator -(Vector32 left, int right)
        => new Vector32(left.X - right, left.Y - right);

    public static Vector32 operator -(int left, Vector32 right)
        => right - left;

    public static implicit operator Vector64(Vector32 vector)
        => new Vector64(vector.X, vector.Y);

    public override string ToString() => $"{{{X}, {Y}}}";

}

public struct Vector64
{

    public Vector64(long x, long y)
    {
        X = x;
        Y = y;
    }

    public long X { get; }
    public long Y { get; }

    public override string ToString() => $"{{{X}, {Y}}}";

}

Przykład

var vector1 = new Vector32(15, 39);
var vector2 = new Vector32(87, 64);
        
Console.WriteLine(vector1 == vector2); // false
Console.WriteLine(vector1 != vector2); // true
Console.WriteLine(vector1 + vector2);  // {102, 103}
Console.WriteLine(vector1 - vector2);  // {-72, -25}

struct

Typ struct to typ wartości, który jest zwykle używany do enkapsulacji małych grup powiązanych zmiennych, takich jak współrzędne prostokąta lub właściwości elementu w ekwipunku.

Klasy są typami referencyjnymi, struktury są typami wartości.

using static System.Console;

namespace ConsoleApplication1
{
    struct Point
    {
        public int X;
        public int Y;

        public override string ToString()
        {
            return $"X = {X}, Y = {Y}";
        }

        public void Display(string name)
        {
            WriteLine(name + ": " + ToString());
        }
    }

    class Program
    {
        static void Main()
        {
            var point1 = new Point {X = 10, Y = 20};
            // it's not a reference but value type
            var point2 = point1;
            point2.X = 777;
            point2.Y = 888;
            point1.Display(nameof(point1)); // point1: X = 10, Y = 20
            point2.Display(nameof(point2)); // point2: X = 777, Y = 888

            ReadKey();
        }
    }
}

Struktury mogą także zawierać konstruktory, stałe, pola, metody, właściwości, indeksatory, operatory, zdarzenia i typy zagnieżdżone, chociaż jeśli potrzebnych jest kilka takich elementów, należy rozważyć utworzenie z tego typu klasy.


Kilka sugestii od MS na temat tego, kiedy użyć struct i kiedy użyć klasy:

ROZWAŻAĆ

definiowanie struktury zamiast klasy, jeśli instancje tego typu są małe i zwykle krótkotrwałe lub są zwykle osadzone w innych obiektach.

UNIKNĄĆ

definiowanie struktury, chyba że typ ma wszystkie następujące cechy:

  • Logicznie reprezentuje pojedynczą wartość, podobną do typów pierwotnych (int, double itp.)
  • Ma rozmiar instancji poniżej 16 bajtów.
  • Jest niezmienny.
  • Nie będzie trzeba go często zapakować.

przełącznik

Instrukcja switch jest instrukcją sterującą, która wybiera sekcję przełącznika do wykonania z listy kandydatów. Instrukcja switch zawiera jedną lub więcej sekcji przełączników. Każda sekcja przełącznika zawiera jedną lub więcej etykiet case , po których następuje jedna lub więcej instrukcji. Jeśli żadna etykieta sprawy nie zawiera pasującej wartości, kontrola jest przenoszona do sekcji default , jeśli taka istnieje. Mówiąc wprost, przypadek nie jest obsługiwany w języku C #. Jeśli jednak co najmniej 1 etykieta case jest pusta, wykonanie nastąpi po kodzie następnego bloku case , który zawiera kod. Umożliwia to grupowanie wielu etykiet case przy tej samej implementacji. W poniższym przykładzie, jeśli month jest równy 12, kod w case 2 zostanie wykonany, ponieważ etykiety case 12 1 i 2 są zgrupowane. Jeśli blok case nie jest pusty, przed następną etykietą case musi być obecny break , w przeciwnym razie kompilator zgłosi błąd.

int month = DateTime.Now.Month; // this is expected to be 1-12 for Jan-Dec

switch (month)
{
    case 12: 
    case 1: 
    case 2:
        Console.WriteLine("Winter");
        break;
    case 3: 
    case 4: 
    case 5:
        Console.WriteLine("Spring");
        break;
    case 6: 
    case 7: 
    case 8:
        Console.WriteLine("Summer");
        break;
    case 9:     
    case 10: 
    case 11:
        Console.WriteLine("Autumn");
        break;
    default:
        Console.WriteLine("Incorrect month index");
        break;
}

case mogą być oznaczone wyłącznie przez wartość znaną w czasie kompilacji (na przykład 1 , "str" , Enum.A ), a zatem variable nie jest ważny case etykiety, ale const lub Enum wartość (a także wszelkie wartość dosłowna).

berło

interface zawiera sygnatury metod, właściwości i zdarzeń. Klasy pochodne definiują członków, ponieważ interfejs zawiera tylko deklarację członków.

Interfejs deklarowany jest za pomocą słowa kluczowego interface .

interface IProduct
{
    decimal Price { get; }
}

class Product : IProduct
{
    const decimal vat = 0.2M;
    
    public Product(decimal price)
    {
        _price = price;
    }
    
    private decimal _price;
    public decimal Price { get { return _price * (1 + vat); } }
}

niebezpieczny

unsafe słowo kluczowe może być użyte w deklaracjach typu lub metod lub do zadeklarowania wbudowanego bloku.

Celem tego słowa kluczowego jest umożliwienie użycia niebezpiecznego podzbioru C # dla danego bloku. Niebezpieczny podzbiór obejmuje takie funkcje, jak wskaźniki, alokacja stosu, tablice podobne do C i tak dalej.

Niebezpieczny kod nie jest weryfikowalny i dlatego jego użycie jest odradzane. Kompilacja niebezpiecznego kodu wymaga przekazania przełącznika do kompilatora C #. Ponadto CLR wymaga pełnego zaufania działającego zestawu.

Pomimo tych ograniczeń, niebezpieczny kod ma poprawne zastosowania w zwiększaniu wydajności niektórych operacji (np. Indeksowanie tablic) lub łatwiejszym (np. Interakcja z niektórymi niezarządzanymi bibliotekami).

Jako bardzo prosty przykład

// compile with /unsafe
class UnsafeTest
{
   unsafe static void SquarePtrParam(int* p)
   {
      *p *= *p; // the '*' dereferences the pointer.
      //Since we passed in "the address of i", this becomes "i *= i"
   }

   unsafe static void Main()
   {
      int i = 5;
      // Unsafe method: uses address-of operator (&):
      SquarePtrParam(&i); // "&i" means "the address of i". The behavior is similar to "ref i"
      Console.WriteLine(i); // Output: 25
   }
}

Podczas pracy ze wskaźnikami możemy zmieniać wartości lokalizacji pamięci bezpośrednio, zamiast konieczności adresowania ich według nazwy. Zauważ, że często wymaga to użycia stałego słowa kluczowego, aby zapobiec możliwemu uszkodzeniu pamięci, ponieważ śmieciarz przesuwa rzeczy (w przeciwnym razie może pojawić się błąd CS0212 ). Ponieważ zmiennej, która została „ustalona”, nie można zapisać, często musimy także mieć drugi wskaźnik, który zaczyna wskazywać w tę samą lokalizację co pierwsza.

void Main()
{
    int[] intArray = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    UnsafeSquareArray(intArray);
    foreach(int i in intArray)
        Console.WriteLine(i);
}

unsafe static void UnsafeSquareArray(int[] pArr)
{
    int len = pArr.Length;

    //in C or C++, we could say
    // int* a = &(pArr[0])
    // however, C# requires you to "fix" the variable first 
    fixed(int* fixedPointer = &(pArr[0]))
    {
        //Declare a new int pointer because "fixedPointer" cannot be written to.
        // "p" points to the same address space, but we can modify it
        int* p = fixedPointer;

        for (int i = 0; i < len; i++)
        {
            *p *= *p; //square the value, just like we did in SquarePtrParam, above
            p++;      //move the pointer to the next memory space.
                      // NOTE that the pointer will move 4 bytes since "p" is an
                      // int pointer and an int takes 4 bytes

            //the above 2 lines could be written as one, like this:
            // "*p *= *p++;"
        }
    }
}

Wynik:

1
4
9
16
25
36
49
64
81
100

unsafe również pozwala na użycie stackalloc, który przydzieli pamięć na stosie jak _alloca w bibliotece wykonawczej C. Możemy zmodyfikować powyższy przykład, aby użyć stackalloc w następujący sposób:

unsafe void Main()
{
    const int len=10;
    int* seedArray = stackalloc int[len];
    
    //We can no longer use the initializer "{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}" as before.
    // We have at least 2 options to populate the array. The end result of either
    // option will be the same (doing both will also be the same here).

    //FIRST OPTION:
    int* p = seedArray; // we don't want to lose where the array starts, so we
                        // create a shadow copy of the pointer
    for(int i=1; i<=len; i++)
        *p++ = i;
    //end of first option

    //SECOND OPTION:
    for(int i=0; i<len; i++)
        seedArray[i] = i+1;
    //end of second option

    UnsafeSquareArray(seedArray, len);
    for(int i=0; i< len; i++)
        Console.WriteLine(seedArray[i]);
}

//Now that we are dealing directly in pointers, we don't need to mess around with
// "fixed", which dramatically simplifies the code
unsafe static void UnsafeSquareArray(int* p, int len)
{
    for (int i = 0; i < len; i++)
        *p *= *p++;
}

(Dane wyjściowe są takie same jak powyżej)

domniemany

implicit słowo kluczowe służy do przeciążenia operatora konwersji. Na przykład możesz zadeklarować klasę Fraction która w razie potrzeby powinna zostać automatycznie przekonwertowana na double , i którą można automatycznie przekonwertować z int :

class Fraction(int numerator, int denominator)
{
    public int Numerator { get; } = numerator;
    public int Denominator { get; } = denominator;
    // ...
    public static implicit operator double(Fraction f)
    {
        return f.Numerator / (double) f.Denominator;
    }
    public static implicit operator Fraction(int i)
    {
        return new Fraction(i, 1);
    }
}

prawda fałsz

true i false słowa kluczowe mają dwa zastosowania:

  1. Jako dosłowne wartości logiczne
var myTrueBool = true;
var myFalseBool = false;
  1. Jako operatory, które mogą być przeciążone
public static bool operator true(MyClass x)
{
    return x.value >= 0;
}

public static bool operator false(MyClass x)
{
    return x.value < 0;
}

Przeciążenie fałszywego operatora było przydatne przed wersją C # 2.0, przed wprowadzeniem typów Nullable .
Typ, który przeciąża true operator, musi również przeciążać false operator.

strunowy

string to alias typu .NET System.String , który umożliwia przechowywanie tekstu (sekwencji znaków).

Notacja:

string a = "Hello";
var b = "world";
var f = new string(new []{ 'h', 'i', '!' }); // hi!

Każdy znak w ciągu jest zakodowany w UTF-16, co oznacza, że każdy znak będzie wymagał minimum 2 bajtów miejsca do przechowywania.

ushort

Typ numeryczny używany do przechowywania 16-bitowych liczb całkowitych dodatnich. ushort jest aliasem dla System.UInt16 i zajmuje 2 bajty pamięci.

Prawidłowy zakres wynosi od 0 do 65535 .

ushort a = 50; // 50
ushort b = 65536; // Error, cannot be converted
ushort c = unchecked((ushort)65536); // Overflows (wraps around to 0)

sbyte

Typ numeryczny używany do przechowywania 8-bitowa liczb całkowitych. sbyte jest aliasem dla System.SByte i zajmuje 1 bajt pamięci. W przypadku niepodpisanego odpowiednika użyj byte .

Prawidłowy zakres to od -127 do 127 (pozostała część służy do przechowywania znaku).

sbyte a = 127; // 127
sbyte b = -127; // -127
sbyte c = 200; // Error, cannot be converted
sbyte d = unchecked((sbyte)129); // -127 (overflows)

var

Zmienna lokalna o niejawnym typie, silnie typowana tak, jakby użytkownik zadeklarował typ. W przeciwieństwie do innych deklaracji zmiennych, kompilator określa typ zmiennej, którą reprezentuje, na podstawie przypisanej mu wartości.

var i = 10; // implicitly typed, the compiler must determine what type of variable this is
int i = 10; // explicitly typed, the type of variable is explicitly stated to the compiler

// Note that these both represent the same type of variable (int) with the same value (10).

W przeciwieństwie do innych typów zmiennych, definicje zmiennych z tym słowem kluczowym muszą zostać zainicjowane po zadeklarowaniu. Wynika to z tego, że słowo kluczowe var reprezentuje zmienną o niejawnym typie.

var i;
i = 10;

// This code will not run as it is not initialized upon declaration.

Słowa kluczowego var można także używać do tworzenia nowych typów danych w locie. Te nowe typy danych są znane jako typy anonimowe . Są one bardzo przydatne, ponieważ pozwalają użytkownikowi zdefiniować zestaw właściwości bez uprzedniego jawnego zadeklarowania dowolnego typu obiektu.

Zwykły anonimowy typ

var a = new { number = 1, text = "hi" };

Zapytanie LINQ, które zwraca typ anonimowy

public class Dog
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public class DogWithBreed
{
    public Dog Dog { get; set; }
    public string BreedName  { get; set; }
}

public void GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
             join b in db.Breeds on d.BreedId equals b.BreedId
             select new 
                    {
                        DogName = d.Name,
                        BreedName = b.BreedName
                    };

    DoStuff(result);
}

Możesz użyć słowa kluczowego var w instrukcji foreach

public bool hasItemInList(List<String> list, string stringToSearch)
{
    foreach(var item in list)
    {
        if( ( (string)item ).equals(stringToSearch) )
            return true;
    }

    return false;
}

delegat

Delegaci to typy reprezentujące odwołanie do metody. Służą do przekazywania metod jako argumentów do innych metod.

Delegaci mogą przechowywać metody statyczne, metody instancji, metody anonimowe lub wyrażenia lambda.

class DelegateExample
{
    public void Run()
    {
        //using class method
        InvokeDelegate( WriteToConsole ); 
        
        //using anonymous method
        DelegateInvoker di = delegate ( string input ) 
        { 
            Console.WriteLine( string.Format( "di: {0} ", input ) );
            return true; 
        };
        InvokeDelegate( di ); 
        
        //using lambda expression
        InvokeDelegate( input => false ); 
    }

    public delegate bool DelegateInvoker( string input );

    public void InvokeDelegate(DelegateInvoker func)
    {
        var ret = func( "hello world" );
        Console.WriteLine( string.Format( " > delegate returned {0}", ret ) );
    }

    public bool WriteToConsole( string input )
    {
        Console.WriteLine( string.Format( "WriteToConsole: '{0}'", input ) );
        return true;
    }
}

Przy przypisywaniu metody do delegata należy pamiętać, że metoda musi mieć ten sam typ zwrotu, a także parametry. Różni się to od przeciążenia metody „normalnej”, gdzie tylko parametry określają sygnaturę metody.

Wydarzenia są budowane na szczycie delegatów.

zdarzenie

event pozwala deweloperowi na implementację wzorca powiadomień.

Prosty przykład

public class Server
{
    // defines the event
    public event EventHandler DataChangeEvent;

    void RaiseEvent()
    {
        var ev = DataChangeEvent;
        if(ev != null)
        {
            ev(this, EventArgs.Empty);
        }
    }
}

public class Client
{
    public void Client(Server server)
    {
        // client subscribes to the server's DataChangeEvent
        server.DataChangeEvent += server_DataChanged;
    }

    private void server_DataChanged(object sender, EventArgs args)
    {
        // notified when the server raises the DataChangeEvent
    }
}

Odniesienie MSDN

częściowy

Słowo kluczowe partial może być użyte podczas definicji typu klasy, struktury lub interfejsu, aby umożliwić podział definicji typu na kilka plików. Jest to przydatne do włączenia nowych funkcji do automatycznie generowanego kodu.

Plik1.cs

namespace A
{
    public partial class Test
    {
        public string Var1 {get;set;}
    }
}

Plik2.cs

namespace A
{
    public partial class Test
    {
        public string Var2 {get;set;}
    }
}

Uwaga: klasę można podzielić na dowolną liczbę plików. Jednak cała deklaracja musi mieć tę samą przestrzeń nazw i to samo zgromadzenie.

Metody można również zadeklarować jako częściowe za pomocą słowa kluczowego partial . W takim przypadku jeden plik będzie zawierał tylko definicję metody, a inny plik będzie zawierał implementację.

Metoda częściowa ma swój podpis zdefiniowany w jednej części typu częściowego, a jego implementacja zdefiniowana w innej części typu. Metody częściowe umożliwiają projektantom klas dostarczanie zaczepów metod, podobnych do procedur obsługi zdarzeń, które programiści mogą zdecydować się wdrożyć lub nie. Jeśli programista nie dostarczy implementacji, kompilator usuwa podpis w czasie kompilacji. Następujące warunki mają zastosowanie do metod częściowych:

  • Podpisy w obu częściach typu częściowego muszą się zgadzać.
  • Metoda musi zostać anulowana.
  • Żadne modyfikatory dostępu nie są dozwolone. Metody częściowe są domyślnie prywatne.

- MSDN

Plik1.cs

namespace A
{
    public partial class Test
    {
        public string Var1 {get;set;}
        public partial Method1(string str);
    }
}

Plik2.cs

namespace A
{
    public partial class Test
    {
        public string Var2 {get;set;}
        public partial Method1(string str)
        {
            Console.WriteLine(str);
        }
    }
}

Uwaga: Typ zawierający metodę częściową należy również zadeklarować jako częściowy.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow