C# Language
Słowa kluczowe
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ą @
.
-
abstract
-
as
-
base
-
bool
-
break
-
byte
-
case
-
catch
-
char
-
checked
-
class
-
const
-
continue
-
decimal
-
default
-
delegate
-
do
-
double
-
else
-
enum
-
event
-
explicit
-
extern
-
false
-
finally
-
fixed
-
float
-
for
-
foreach
-
goto
-
if
-
implicit
-
in
-
int
-
interface
-
internal
-
is
-
lock
-
long
-
namespace
-
new
-
null
-
object
-
operator
-
out
-
override
-
params
-
private
-
protected
-
public
-
readonly
-
ref
-
return
-
sbyte
-
sealed
-
short
-
sizeof
-
stackalloc
-
static
-
string
-
struct
-
switch
-
this
-
throw
-
true
-
try
-
typeof
-
uint
-
ulong
-
unchecked
-
unsafe
-
ushort
-
using
(dyrektywa) -
using
(instrukcja) -
virtual
-
void
-
volatile
-
when
-
while
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() {volatileint 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
ibool
- typy
sbyte
na podstawiebyte
,sbyte
,short
,ushort
,int
lubuint
-
IntPtr
iUIntPtr
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 jakInterlocked.Read
iInterlocked.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:
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
:
za pomocą instrukcji :
using
kluczoweusing
zapewnia, że obiekty implementujące interfejsIDisposable
są odpowiednio usuwane po użyciu. Instrukcja użycia zawiera osobny tematza 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 metodaBaseClass
- Jeśli obiekt jest zadeklarowany typu
DerivedClass
wówczas wykonywana jest metodaDerivedClass
.
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);
}
W C # 5.0 await
nie może być użyte w catch
i finally
.
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.
-
char c = 'c';
-
char c = '\u0063'; //Unicode
-
char c = '\x0063'; //Hex
-
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:
- 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.
}
- 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:
- Jako dosłowne wartości logiczne
var myTrueBool = true;
var myFalseBool = false;
- 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
}
}
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.