C# Language
Schlüsselwörter
Suche…
Einführung
Schlüsselwörter sind vordefinierte, reservierte Bezeichner mit besonderer Bedeutung für den Compiler. Sie können in Ihrem Programm nicht ohne das @
-Zeichen als Bezeichner verwendet werden. Beispielsweise ist @if
ein gesetzlicher Bezeichner, aber nicht das Schlüsselwort if
.
Bemerkungen
C # hat eine vordefinierte Sammlung von "Schlüsselwörtern" (oder reservierten Wörtern), die jeweils eine spezielle Funktion haben. Diese Wörter können nicht als Bezeichner (Namen für Variablen, Methoden, Klassen usw.) verwendet werden, sofern nicht das @
vorangestellt ist.
-
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
(Direktive) -
using
(Anweisung) -
virtual
-
void
-
volatile
-
when
-
while
Abgesehen davon verwendet C # auch einige Schlüsselwörter, um dem Code eine bestimmte Bedeutung zu geben. Sie werden als kontextabhängige Schlüsselwörter bezeichnet. Kontextbezogene Schlüsselwörter können als Bezeichner verwendet werden und müssen nicht mit @
vorangestellt werden, wenn sie als Bezeichner verwendet werden.
stackalloc
Das Schlüsselwort stackalloc
erstellt einen Speicherbereich auf dem Stapel und gibt einen Zeiger auf den Anfang dieses Speichers zurück. Der zugewiesene Stapelspeicher wird automatisch entfernt, wenn der Bereich, in dem er erstellt wurde, beendet wird.
//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;
...
Wird in einem unsicheren Kontext verwendet.
Wie bei allen Zeigern in C # gibt es keine Einschränkungen für Lesevorgänge und Zuweisungen. Das Lesen über die Grenzen des zugewiesenen Speichers hinaus hat unvorhersehbare Ergebnisse - es kann auf eine beliebige Stelle im Speicher zugegriffen werden oder es kann eine Zugriffsverletzung auftreten.
//Allocate 1 byte
byte* ptr = stackalloc byte[1];
//Unpredictable results...
ptr[10] = 1;
ptr[-1] = 2;
Der zugewiesene Stapelspeicher wird automatisch entfernt, wenn der Bereich, in dem er erstellt wurde, beendet wird. Dies bedeutet, dass Sie den mit stackalloc erstellten Speicher niemals zurückgeben oder außerhalb der Gültigkeitsdauer des Bereichs speichern sollten.
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
kann nur beim Deklarieren und Initialisieren von Variablen verwendet werden. Folgendes ist nicht gültig:
byte* ptr;
...
ptr = stackalloc byte[1024];
Bemerkungen:
stackalloc
sollte nur für Leistungsoptimierungen verwendet werden (entweder für die Berechnung oder für Interop). Dies liegt an der Tatsache, dass:
- Der Garbage Collector ist nicht erforderlich, da der Speicher auf dem Stack und nicht auf dem Heap reserviert wird. Der Speicher wird freigegeben, sobald die Variable den Gültigkeitsbereich verlässt
- Es ist schneller, Speicher auf dem Stapel als dem Heap zuzuweisen
- Erhöhen Sie die Wahrscheinlichkeit von Cache-Treffern auf der CPU aufgrund der Datenlokalität
flüchtig
Das Hinzufügen des volatile
Schlüsselworts zu einem Feld zeigt dem Compiler an, dass der Feldwert von mehreren separaten Threads geändert werden kann. Der Hauptzweck des volatile
Schlüsselworts besteht darin, Compiler-Optimierungen zu verhindern, die nur einen Single-Thread-Zugriff voraussetzen. Durch die Verwendung von volatile
sichergestellt, dass der Wert des Felds der aktuellste verfügbare Wert ist und der Wert nicht den Zwischenspeichern unterliegt, die nichtflüchtige Werte sind.
Es empfiehlt sich, jede Variable , die von mehreren Threads verwendet werden kann, als volatile
zu markieren, um unerwartetes Verhalten aufgrund von Optimierungen hinter den Kulissen zu verhindern. Betrachten Sie den folgenden Codeblock:
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);
}
}
Im obigen Codeblock liest der Compiler die Anweisungen x = 5
und y = x + 10
und bestimmt, dass der Wert von y
immer auf 15 endet. Daher optimiert er die letzte Anweisung als y = 15
. Die Variable x
ist jedoch tatsächlich ein public
Feld, und der Wert von x
kann zur Laufzeit durch einen anderen Thread geändert werden, der separat auf dieses Feld wirkt. Betrachten Sie nun diesen modifizierten Codeblock. Beachten Sie, dass das Feld x
jetzt als volatile
deklariert ist.
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);
}
}
Nun sucht der Compiler für Lese Verwendungen des Feldes x
und stellt sicher , dass der aktuelle Wert des Feldes immer abgerufen wird. Dadurch wird sichergestellt, dass der aktuelle Wert von x
immer abgerufen wird, wenn mehrere Threads dieses Feld lesen und in dieses Feld schreiben.
volatile
kann nur für Felder innerhalb von class
oder struct
. Folgendes ist nicht gültig :
public void MyMethod() {volatileint x; }
volatile
kann nur auf Felder folgender Typen angewendet werden:
- Referenztypen oder generische Typenparameter, die als Referenztypen bekannt sind
- primitive Typen wie
sbyte
,byte
,short
,ushort
,int
,uint
,char
,float
undbool
- Aufzählungstypen basierend auf
byte
,sbyte
,short
,ushort
,int
oderuint
-
IntPtr
undUIntPtr
Bemerkungen:
- Der
volatile
Modifikator wird normalerweise für ein Feld verwendet, auf das mehrere Threads zugreifen, ohne die Sperranweisung zum Serialisieren des Zugriffs zu verwenden. - Das
volatile
Schlüsselwort kann auf Felder mit Referenztypen angewendet werden - Das
volatile
Schlüsselwort kann nicht auf 64-Bit-Grundelementen auf einem 32-Bit-Plattformatom ausgeführt werden. Interlock-Vorgänge wieInterlocked.Read
undInterlocked.Exchange
müssen weiterhin für den sicheren Multithread-Zugriff auf diesen Plattformen verwendet werden.
Fest
Die feste Anweisung fixiert den Speicher an einem Ort. Objekte im Speicher bewegen sich normalerweise in der Nähe. Dies ermöglicht das Sammeln von Müll. Wenn wir jedoch unsichere Zeiger auf Speicheradressen verwenden, darf dieser Speicher nicht verschoben werden.
- Wir verwenden die fixed-Anweisung, um sicherzustellen, dass der Garbage Collector die Zeichenfolgendaten nicht verlagert.
Feste Variablen
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.
}
Wird in einem unsicheren Kontext verwendet.
Feste Array-Größe
unsafe struct Example
{
public fixed byte SomeField[8];
public fixed char AnotherField[64];
}
fixed
kann nur für Felder in einer struct
(muss auch in einem unsicheren Kontext verwendet werden).
Standard
Für Klassen, Interfaces, Delegate, Array, nullfähige (wie int?) Und default(TheType)
gibt default(TheType)
null
:
class MyClass {}
Debug.Assert(default(MyClass) == null);
Debug.Assert(default(string) == null);
Bei Strukturen und Aufzählungen gibt default(TheType)
das gleiche wie das 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)
kann besonders nützlich sein, wenn T
ein generischer Parameter ist, für den keine Einschränkung vorhanden ist, um zu entscheiden, ob T
ein Referenztyp oder ein Werttyp ist. Beispiel:
public T GetResourceOrDefault<T>(string resourceName)
{
if (ResourceExists(resourceName))
{
return (T)GetResource(resourceName);
}
else
{
return default(T);
}
}
schreibgeschützt
Das Schlüsselwort readonly
ist ein readonly
. Wenn eine readonly
einen readonly
Modifizierer enthält, können Zuweisungen zu diesem Feld nur als Teil der Deklaration oder in einem Konstruktor in derselben Klasse erfolgen.
Das Schlüsselwort readonly
unterscheidet sich vom Schlüsselwort const
. Ein const
Feld kann nur bei der Deklaration des Feldes initialisiert werden. Ein readonly
Feld kann entweder bei der Deklaration oder in einem Konstruktor initialisiert werden. readonly
Felder können daher abhängig vom verwendeten Konstruktor unterschiedliche Werte haben.
Das Schlüsselwort readonly
wird häufig verwendet, wenn Abhängigkeiten readonly
.
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
}
}
Hinweis: Das Deklarieren eines Felds als Readonly bedeutet keine Unveränderlichkeit . Wenn das Feld ein Referenztyp ist, kann der Inhalt des Objekts geändert werden. Readonly wird normalerweise verwendet, um zu verhindern, dass das Objekt nur während der Instantiierung dieses Objekts überschrieben und zugewiesen wird.
Anmerkung: Innerhalb des Konstruktors kann ein Readonly-Feld neu zugewiesen werden
public class Car
{
public double Speed {get; set;}
}
//In code
private readonly Car car = new Car();
private void SomeMethod()
{
car.Speed = 100;
}
wie
Das as
Schlüsselwort ist ein Operator, der einem Cast ähnelt. Wenn eine InvalidCastException
nicht möglich ist, führt die Verwendung von as
null
und nicht zu einer InvalidCastException
.
expression as type
entspricht expression is type ? (type)expression : (type)null
mit der Einschränkung, die as
nur für Referenzkonvertierungen, nullfähige Konvertierungen und Boxkonvertierungen gültig ist. Benutzerdefinierte Konvertierungen werden nicht unterstützt. Stattdessen muss ein normaler Abguss verwendet werden.
Bei der obigen Erweiterung generiert der Compiler Code, sodass der expression
nur einmal ausgewertet wird und eine dynamische Überprüfung des Typs verwendet (im Gegensatz zu den beiden im obigen Beispiel).
as
kann nützlich sein, wenn ein Argument erwartet wird, um mehrere Typen zu vereinfachen. Insbesondere räumt er die mehrere Optionen Benutzer - und nicht mit jeder Möglichkeit , die Überprüfung is
vor dem Gießen, oder einfach nur Gießen und Ausnahmen zu kontrollieren. Es ist empfehlenswert, "as" beim Casting / Check eines Objekts zu verwenden, was nur eine Unboxing-Strafe verursacht. Unter Verwendung is
zu überprüfen, dann Gießen zwei Unboxing Strafen führen.
Wenn erwartet wird, dass ein Argument eine Instanz eines bestimmten Typs ist, wird eine regelmäßige Besetzung bevorzugt, da der Zweck des Lesers für den Leser klarer ist.
Da ein Aufruf von as
null
, überprüfen Sie immer das Ergebnis, um eine NullReferenceException
zu vermeiden.
Verwendungsbeispiel
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);
Gleichwertiges Beispiel ohne Verwendung as
:
Console.WriteLine(something is string ? (string)something : (string)null);
Dies ist hilfreich, wenn Sie die Equals
Funktion in benutzerdefinierten Klassen überschreiben.
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
}
}
ist
Überprüft, ob ein Objekt mit einem bestimmten Typ kompatibel ist, dh ob ein Objekt eine Instanz des Typs BaseInterface
oder ein Typ ist, der von 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
Wenn die Besetzung die Absicht hat, das Objekt zu verwenden, ist es am besten, das as
Schlüsselwort zu verwenden. '
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
}
Ab C # 7 erweitert die pattern matching
Funktion den Operator, um nach einem Typ zu suchen und gleichzeitig eine neue Variable zu deklarieren. Gleicher Codeteil mit C # 7:
if(d is BaseClass asD ){
asD.Method();
}
Art der
Gibt den Type
eines Objekts zurück, ohne dass es instanziiert werden muss.
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
wird verwendet, um Werte darzustellen, die sich während der gesamten Lebensdauer des Programms nicht ändern . Ihr Wert ist ab der Kompilierungszeit konstant, im Gegensatz zum Schlüsselwort readonly
, dessen Wert ab Laufzeit konstant ist.
Da sich beispielsweise die Lichtgeschwindigkeit niemals ändert, können wir sie konstant speichern.
const double c = 299792458; // Speed of light
double CalculateEnergy(double mass)
{
return mass * c * c;
}
Dies ist im Wesentlichen das gleiche wie die return mass * 299792458 * 299792458
, da der Compiler direkt c
durch seinen konstanten Wert ersetzt.
Daher kann c
nach der Deklaration nicht mehr geändert werden. Folgendes wird einen Fehler bei der Kompilierung verursachen:
const double c = 299792458; // Speed of light
c = 500; //compile-time error
Einer Konstante können dieselben Zugriffsmodifizierer wie Methoden vorangestellt werden:
private const double c = 299792458;
public const double c = 299792458;
internal const double c = 299792458;
const
Mitglieder sind von Natur aus static
. Die Verwendung von static
ist jedoch ausdrücklich nicht zulässig.
Sie können auch method-lokale Konstanten definieren:
double CalculateEnergy(double mass)
{
const c = 299792458;
return mass * c * c;
}
Diesen kann kein private
oder public
Schlüsselwort vorangestellt werden, da sie implizit lokal für die Methode sind, in der sie definiert sind.
In einer const
Deklaration können nicht alle Typen verwendet werden. Die zulässigen Wertetypen sind die vordefinierten Typen sbyte
, byte
, short
, ushort
, int
, uint
, long
, ulong
, char
, float
, double
, decimal
, bool
und alle enum
. Der Versuch, const
Member mit anderen Wertetypen (wie TimeSpan
oder Guid
) zu deklarieren, TimeSpan
zur Kompilierzeit fehl.
Für den speziellen Referenztyp vordefinierte string
, können Konstanten mit einem beliebigen Wert deklariert werden. Für alle anderen Referenztypen können Konstanten deklariert werden, müssen jedoch immer den Wert null
.
Da const
Werte zum Zeitpunkt der Kompilierung bekannt sind, werden sie als zulässig case
Etikett in einer switch
- Anweisung als Standardargumente für optionale Parameter als Argumente Spezifikationen zuzuschreiben, und so weiter.
Wenn const
Werte in verschiedenen Baugruppen verwendet werden, muss bei der Versionsverwaltung sorgfältig vorgegangen werden. Wenn beispielsweise Assembly A eine public const int MaxRetries = 3;
und Assembly B verwendet diese Konstante. Wenn der Wert von MaxRetries
später in Assembly A auf 5
geändert wird (was dann erneut kompiliert wird), ist diese Änderung in Assembly B nicht wirksam, es sei denn, Assembly B wird ebenfalls neu kompiliert (mit ein Verweis auf die neue Version von A).
Aus diesem Grunde , wenn ein Wert könnte in zukünftigen Revisionen des Programms ändern, und wenn der Wert öffentlich sichtbar sein muss, nicht diesen Wert erklären const
, es sei denn Sie wissen , dass alle abhängigen Baugruppen werden neu kompiliert werden , wenn etwas geändert wird. Die Alternative ist die Verwendung von static readonly
anstelle von const
, das zur Laufzeit aufgelöst wird.
Namensraum
Das namespace
Schlüsselwort ist ein Organisationskonstrukt, das uns hilft, die Anordnung einer Codebasis zu verstehen. Namespaces in C # sind virtuelle Räume und nicht in einem physischen Ordner.
namespace StackOverflow
{
namespace Documentation
{
namespace CSharp.Keywords
{
public class Program
{
public static void Main()
{
Console.WriteLine(typeof(Program).Namespace);
//StackOverflow.Documentation.CSharp.Keywords
}
}
}
}
}
Namensräume in C # können auch in verketteter Syntax geschrieben werden. Folgendes entspricht dem obigen:
namespace StackOverflow.Documentation.CSharp.Keywords
{
public class Program
{
public static void Main()
{
Console.WriteLine(typeof(Program).Namespace);
//StackOverflow.Documentation.CSharp.Keywords
}
}
}
versuchen, fangen, endlich werfen
try
, catch
, finally
und throw
erlauben es Ihnen, Ausnahmen in Ihrem Code zu behandeln.
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();
}
Hinweis: Das Schlüsselwort return
kann im try
Block verwendet werden, und der finally
Block wird noch ausgeführt (unmittelbar vor der Rückkehr). Zum Beispiel:
try
{
connection.Open();
return connection.Get(query);
}
finally
{
connection.Close();
}
Die Anweisung connection.Close()
wird ausgeführt, bevor das Ergebnis von connection.Get(query)
zurückgegeben wird.
fortsetzen
Übergeben Sie die Kontrolle sofort an die nächste Iteration des umgebenden Schleifenkonstrukts (for, for, do, while):
for (var i = 0; i < 10; i++)
{
if (i < 5)
{
continue;
}
Console.WriteLine(i);
}
Ausgabe:
5
6
7
8
9
var stuff = new [] {"a", "b", null, "c", "d"};
foreach (var s in stuff)
{
if (s == null)
{
continue;
}
Console.WriteLine(s);
}
Ausgabe:
ein
b
c
d
ref, raus
Die Schlüsselwörter ref
und out
bewirken, dass ein Argument als Verweis übergeben wird, nicht als Wert. Für Werttypen bedeutet dies, dass der Wert der Variablen vom Aufseher geändert werden kann.
int x = 5;
ChangeX(ref x);
// The value of x could be different now
Bei Referenztypen kann die Instanz in der Variablen nicht nur geändert werden (wie bei ref
), sondern auch vollständig ersetzt werden:
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
Der Hauptunterschied zwischen dem out
und ref
- Schlüsselwort ist , dass ref
die Variable erfordert vom Anrufer initialisiert wird, während out
geht die Verantwortung an den Angerufenen.
Um einen out
Parameter zu verwenden, müssen sowohl die Methodendefinition als auch die aufrufende Methode explizit das out
Schlüsselwort verwenden.
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;
}
Folgendes wird nicht kompiliert, da für out
Parameter ein Wert zugewiesen werden muss, bevor die Methode zurückgegeben wird (dies würde stattdessen mit ref
kompiliert werden):
void PrintByOut(out int value)
{
Console.WriteLine("Hello!");
}
out-Schlüsselwort als generischer Modifikator verwenden
out
Schlüsselwort kann auch in generischen Typparametern verwendet werden, wenn generische Schnittstellen und Delegaten definiert werden. In diesem Fall gibt das Schlüsselwort out
an, dass der Typparameter kovariant ist.
Mit der Kovarianz können Sie einen stärker abgeleiteten Typ als den durch den generischen Parameter angegebenen verwenden. Dies ermöglicht die implizite Konvertierung von Klassen, die Variantenschnittstellen implementieren, und die implizite Konvertierung von Delegattypen. Kovarianz und Kontravarianz werden für Referenztypen unterstützt, für Werttypen jedoch nicht. - 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
geprüft, nicht geprüft
Die checked
und unchecked
Schlüsselwörter definieren, wie Operationen den mathematischen Überlauf behandeln. "Überlauf" im Kontext der checked
und unchecked
Schlüsselwörter liegt vor, wenn eine ganzzahlige arithmetische Operation zu einem Wert führt, dessen Größe größer ist, als der Zieldatentyp darstellen kann.
Wenn in einem checked
Block ein Überlauf auftritt (oder wenn der Compiler so eingestellt ist, dass die überprüfte Arithmetik global verwendet wird), wird eine Ausnahme ausgelöst, um vor unerwünschtem Verhalten zu warnen. Währenddessen ist der Überlauf in einem unchecked
Block stumm: Es werden keine Ausnahmen ausgelöst, und der Wert wird einfach an die gegenüberliegende Grenze verschoben. Dies kann zu subtilen, schwer zu findenden Fehlern führen.
Da die meisten Rechenoperationen für Werte ausgeführt werden, die nicht groß oder klein genug sind, um überzulaufen, ist es meistens nicht erforderlich, einen Block explizit als checked
zu definieren. Bei der Arithmetik von unbegrenzten Eingaben, die zu einem Überlauf führen können, ist Vorsicht geboten, z. B. bei rekursiven Funktionen oder während der Benutzereingaben.
Weder checked
noch unchecked
wirken sich Gleitkomma-Rechenoperationen aus.
Wenn ein Block oder Ausdruck als unchecked
deklariert ist, können alle darin enthaltenen Rechenoperationen überlaufen, ohne dass ein Fehler auftritt. Ein Beispiel, bei dem dieses Verhalten gewünscht wird, wäre die Berechnung einer Prüfsumme, bei der der Wert während der Berechnung "umlaufen" darf:
byte Checksum(byte[] data) {
byte result = 0;
for (int i = 0; i < data.Length; i++) {
result = unchecked(result + data[i]); // unchecked expression
}
return result;
}
Eine der häufigsten Anwendungen für unchecked
ist das Implementieren einer benutzerdefinierten Überschreibung für object.GetHashCode()
, eine Art von Prüfsumme. Sie sehen die Verwendung des Keywords in den Antworten auf diese Frage: Welches ist der beste Algorithmus für ein überschriebenes System.Object.GetHashCode? .
Wenn ein Block oder Ausdruck als checked
deklariert wird, führt jede arithmetische Operation, die einen Überlauf verursacht, zur Auslösung einer OverflowException
.
int SafeSum(int x, int y) {
checked { // checked block
return x + y;
}
}
Sowohl das Kontrollkästchen als auch das Kontrollkästchen können Block und Ausdruck sein.
Geprüfte und ungeprüfte Blöcke wirken sich nicht auf aufgerufene Methoden aus, sondern nur auf Operatoren, die in der aktuellen Methode direkt aufgerufen werden. Beispielsweise sind Enum.ToObject()
, Convert.ToInt32()
und benutzerdefinierte Operatoren nicht von benutzerdefinierten überprüften / ungeprüften Kontexten betroffen.
Hinweis : Das standardmäßige Überlauf-Standardverhalten (geprüft oder nicht markiert) kann in den Projekteigenschaften oder über die Befehlszeilenschalter / / [+ | -] geändert werden. Üblicherweise werden standardmäßig Vorgänge für Debug-Builds und für Release-Builds nicht aktiviert. Die checked
und unchecked
Schlüsselwörter werden nur dann verwendet, wenn ein Standardansatz nicht angewendet wird und Sie ein explizites Verhalten benötigen, um die Korrektheit sicherzustellen.
gehe zu
goto
können Sie zu einer bestimmten Zeile innerhalb des Codes springen, die durch ein Label angegeben wird.
goto
als ein:
Etikette:
void InfiniteHello()
{
sayHello:
Console.WriteLine("Hello!");
goto sayHello;
}
Fallerklärung:
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
}
Dies ist besonders bei der Ausführung mehrerer Verhalten in einer switch-Anweisung hilfreich, da C # keine Fall-Through-Case-Blöcke unterstützt .
Ausnahme wiederholen
var exCount = 0;
retry:
try
{
//Do work
}
catch (IOException)
{
exCount++;
if (exCount < 3)
{
Thread.Sleep(100);
goto retry;
}
throw;
}
Ähnlich wie in vielen Sprachen wird von der Verwendung des goto-Keywords abgesehen von den unten aufgeführten Fällen abgeraten.
Gültige Verwendungen von goto
die für C # gelten:
Fallfall in switch-Anweisung.
Mehrstufige Pause. LINQ kann stattdessen häufig verwendet werden, weist jedoch normalerweise eine schlechtere Leistung auf.
Ressourcenfreigabe bei der Arbeit mit unverpackten Objekten auf niedriger Ebene. In C # sollten Low-Level-Objekte normalerweise in separaten Klassen eingeschlossen werden.
Finite-State-Maschinen, zum Beispiel Parser; Wird vom Compiler intern verwendet und generiert async / await-Zustandsmaschinen.
enum
Das Schlüsselwort enum
teilt dem Compiler mit, dass diese Klasse von der abstrakten Klasse Enum
erbt, ohne dass der Programmierer sie explizit erben muss. Enum
ist ein Nachkomme von ValueType
, der für die Verwendung mit verschiedenen Konstanten benannter ValueType
vorgesehen ist.
public enum DaysOfWeek
{
Monday,
Tuesday,
}
Sie können optional einen bestimmten Wert für jeden (oder einige davon) angeben:
public enum NotableYear
{
EndOfWwI = 1918;
EnfOfWwII = 1945,
}
In diesem Beispiel habe ich einen Wert für 0 weggelassen. Dies ist normalerweise eine schlechte Praxis. Eine enum
hat immer einen Standardwert, der durch explizite Konvertierung (YourEnumType) 0
, wobei YourEnumType
Ihnen angegebene enume
. Wenn der Wert 0 nicht definiert ist, hat eine enum
zu Beginn keinen definierten Wert.
Der standardmäßig zugrunde liegende Typ der enum
ist int
. Sie können den zugrunde liegenden Typ in einen beliebigen ganzzahligen Typ sbyte
, einschließlich byte
, sbyte
, short
, ushort
, int
, uint
, long
und ulong
. Nachfolgend finden Sie eine Aufzählung mit dem zugrunde liegenden Typ- byte
:
enum Days : byte
{
Sunday = 0,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
};
Beachten Sie auch, dass Sie einfach mit einem Cast in den zugrunde liegenden Typ konvertieren können:
int value = (int)NotableYear.EndOfWwI;
Aus diesen Gründen sollten Sie immer überprüfen, ob eine enum
gültig ist, wenn Sie Bibliotheksfunktionen verfügbar machen:
void PrintNotes(NotableYear year)
{
if (!Enum.IsDefined(typeof(NotableYear), year))
throw InvalidEnumArgumentException("year", (int)year, typeof(NotableYear));
// ...
}
Base
Das base
wird verwendet, um auf Mitglieder einer Basisklasse zuzugreifen. Sie wird häufig verwendet, um Basisimplementierungen von virtuellen Methoden aufzurufen oder um anzugeben, welcher Basiskonstruktor aufgerufen werden soll.
Einen Konstruktor auswählen
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);
}
}
Aufruf der Basisimplementierung der virtuellen Methode
public override void SomeVirtualMethod() {
// Do something, then call base implementation
base.SomeVirtualMethod();
}
Es ist möglich, das Basisschlüsselwort zu verwenden, um eine Basisimplementierung von einer beliebigen Methode aufzurufen. Dadurch wird der Methodenaufruf direkt an die Basisimplementierung gebunden. Dies bedeutet, dass selbst wenn neue untergeordnete Klassen eine virtuelle Methode überschreiben, die Basisimplementierung weiterhin aufgerufen wird. Daher muss diese mit Vorsicht verwendet werden.
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());
}
}
für jeden
foreach
wird verwendet, um die Elemente eines Arrays oder die Elemente in einer Auflistung zu IEnumerable
die IEnumerable
✝ implementiert.
var lines = new string[] {
"Hello world!",
"How are you doing today?",
"Goodbye"
};
foreach (string line in lines)
{
Console.WriteLine(line);
}
Dies wird ausgegeben
"Hallo Welt!"
"Wie geht es dir heute?"
"Auf Wiedersehen"
Sie können die foreach
Schleife jederzeit mit dem Schlüsselwort break beenden oder mit dem Schlüsselwort continue zur nächsten Iteration übergehen.
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,
Beachten Sie, dass die Reihenfolge der Iteration nur für bestimmte Sammlungen wie Arrays und List
garantiert wird, für viele andere Sammlungen jedoch nicht .
IEnumerable
Während IEnumerable
normalerweise zum IEnumerable
aufzählbaren Sammlungen verwendet wird, erfordert foreach
nur, dass die object GetEnumerator()
öffentlich object GetEnumerator()
Methode sollte ein Objekt zurückgeben, das die bool MoveNext()
Methode bool MoveNext()
und das object Current { get; }
Eigenschaft.
Params
params
kann ein Methodenparameter eine variable Anzahl von Argumenten erhalten, dh für diesen Parameter sind null, ein oder mehrere Argumente zulässig.
static int AddAll(params int[] numbers)
{
int total = 0;
foreach (int number in numbers)
{
total += number;
}
return total;
}
Diese Methode kann jetzt mit einer typischen Liste von int
Argumenten oder einem Array von Ints aufgerufen werden.
AddAll(5, 10, 15, 20); // 50
AddAll(new int[] { 5, 10, 15, 20 }); // 50
params
müssen höchstens einmal vorkommen. Wenn sie verwendet werden, muss sie die letzte in der Argumentliste sein, auch wenn der nachfolgende Typ sich vom Array unterscheidet.
Seien Sie vorsichtig beim Überladen von Funktionen, wenn Sie das Schlüsselwort params
. C # zieht es vor, spezifischere Überladungen zu finden, bevor Sie versuchen, Überladungen mit params
. Zum Beispiel, wenn Sie zwei Methoden haben:
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;
}
Dann hat die Überladung des spezifischen Arguments 2 Vorrang, bevor die params
Überladung versucht wird.
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)
brechen
In einer Schleife (for, do, while) break
die break
Anweisung die Ausführung der innersten Schleife ab und kehrt zum Code zurück. Es kann auch mit yield
in dem angegeben wird, dass ein Iterator beendet ist.
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.");
}
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);
}
Die break-Anweisung wird auch in Switch-Case-Konstrukten verwendet, um aus einem Case oder einem Standardsegment auszubrechen.
switch(a)
{
case 5:
Console.WriteLine("a was 5!");
break;
default:
Console.WriteLine("a was something else!");
break;
}
In switch-Anweisungen ist das Schlüsselwort 'break' am Ende jeder case-Anweisung erforderlich. Dies steht im Gegensatz zu einigen Sprachen, die das Durchfallen der nächsten Fallaussagen in der Serie ermöglichen. Problemumgehungen hierfür umfassen "goto" -Anweisungen oder das aufeinanderfolgende Stapeln der "case" -Anweisungen.
Der folgende Code gibt die Zahlen 0, 1, 2, ..., 9
und die letzte Zeile wird nicht ausgeführt. yield break
bedeutet das Ende der Funktion (nicht nur eine Schleife).
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");
}
Beachten Sie, dass es im Gegensatz zu anderen Sprachen nicht möglich ist, einen bestimmten Bruch in C # zu kennzeichnen. Dies bedeutet, dass bei verschachtelten Schleifen nur die innerste Schleife angehalten wird:
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;
}
}
Wenn Sie hier aus der äußeren Schleife ausbrechen möchten, können Sie verschiedene Strategien verwenden, z.
- Eine goto- Anweisung, um aus der gesamten Schleifenstruktur zu springen.
- Eine bestimmte Flag-Variable (
shouldBreak
im folgenden Beispiel "shouldBreak
), die am Ende jeder Iteration der äußeren Schleife überprüft werden kann. - Umgestaltung des Codes zur Verwendung einer
return
Anweisung im innersten Schleifenrumpf oder Vermeidung der gesamten geschachtelten Schleifenstruktur insgesamt.
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
abstrakt
Eine mit dem Keyword abstract
gekennzeichnete Klasse kann nicht instanziiert werden.
Eine Klasse muss als abstrakt markiert sein, wenn sie abstrakte Elemente enthält oder wenn sie abstrakte Elemente erbt, die sie nicht implementiert. Eine Klasse kann als abstrakt markiert werden, auch wenn keine abstrakten Mitglieder beteiligt sind.
Abstrakte Klassen werden normalerweise als Basisklassen verwendet, wenn ein Teil der Implementierung von einer anderen Komponente angegeben werden muss.
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
Eine mit dem Schlüsselwort abstract
gekennzeichnete Methode, Eigenschaft oder Ereignis zeigt an, dass die Implementierung für dieses Mitglied in einer Unterklasse bereitgestellt werden muss. Wie oben erwähnt, können abstrakte Member nur in abstrakten Klassen angezeigt werden.
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; }
}
Float, doppelt, dezimal
schweben
float
ist ein Alias für den .NET-Datentyp System.Single
. Es ermöglicht die Speicherung von Gleitkommazahlen nach IEEE 754 mit einfacher Genauigkeit. Dieser Datentyp ist in der mscorlib.dll
vorhanden, auf die jedes C # -Projekt beim Erstellen implizit verweist.
Ungefährer Bereich: -3,4 × 10 38 bis 3,4 × 10 38
Dezimalgenauigkeit: 6-9 signifikante Stellen
Notation :
float f = 0.1259;
var f1 = 0.7895f; // f is literal suffix to represent float values
Es ist zu beachten, dass der
float
Typ häufig zu erheblichen Rundungsfehlern führt. Bei Anwendungen, bei denen Präzision wichtig ist, sollten andere Datentypen berücksichtigt werden.
doppelt
double
ist ein Alias für den .NET-Datentyp System.Double
. Es ist eine 64-Bit-Gleitkommazahl mit doppelter Genauigkeit. Dieser Datentyp ist in der mscorlib.dll
vorhanden, auf die in einem C # -Projekt implizit verwiesen wird.
Bereich: ± 5,0 × 10 –324 bis ± 1,7 × 10 308
Dezimalgenauigkeit: 15-16 signifikante Stellen
Notation :
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
Dezimal
decimal
ist ein Alias für den .NET-Datentyp System.Decimal
. Es stellt ein Schlüsselwort dar, das einen 128-Bit-Datentyp angibt. Im Vergleich zu Gleitkommatypen hat der Dezimaltyp eine höhere Genauigkeit und einen kleineren Bereich, wodurch er für finanzielle und monetäre Berechnungen geeignet ist. Dieser Datentyp ist in der mscorlib.dll
vorhanden, auf die in einem C # -Projekt implizit verwiesen wird.
Bereich: -7,9 × 10 28 bis 7,9 × 10 28
Dezimalgenauigkeit: 28-29 signifikante Stellen
Notation :
decimal payable = 152.25m; // a decimal value
var marks = 754.24m; // m is literal suffix to represent decimal values
uint
Eine vorzeichenlose Ganzzahl oder Uint ist ein numerischer Datentyp, der nur positive Ganzzahlen enthalten kann. Wie der Name vermuten lässt, handelt es sich um eine vorzeichenlose 32-Bit-Ganzzahl. Das Schlüsselwort uint selbst ist ein Alias für den System.UInt32
Typ System.UInt32
. Dieser Datentyp ist in der mscorlib.dll
, auf die jedes C # -Projekt beim Erstellen implizit verweist. Es belegt vier Byte Speicherplatz.
Vorzeichenlose Ganzzahlen können jeden Wert von 0 bis 4.294.967.295 enthalten.
Beispiele wie und jetzt nicht vorzeichenlose Ganzzahlen deklariert werden
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
Bitte beachten Sie: Laut Microsoft wird empfohlen, den int- Datentyp nach Möglichkeit zu verwenden, da der Uint- Datentyp nicht CLS-kompatibel ist.
diese
Das Schlüsselwort this
bezieht sich auf die aktuelle Instanz von class (object). Auf diese Weise können zwei Variablen mit demselben Namen unterschieden werden, eine auf Klassenebene (ein Feld) und eine, die ein Parameter (oder eine lokale Variable) einer Methode ist.
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;
}
}
Andere Verwendungen des Schlüsselworts sind das Verketten von nicht statischen Konstruktorüberladungen :
public MyClass(int arg) : this(arg, null)
{
}
und Indexer schreiben:
public string this[int idx1, string idx2]
{
get { /* ... */ }
set { /* ... */ }
}
und deklarieren von Erweiterungsmethoden :
public static int Count<TItem>(this IEnumerable<TItem> source)
{
// ...
}
Wenn es keinen Konflikt mit einer lokalen Variablen oder einem lokalen Parameter gibt, ist es eine Frage des Stils, ob this
soll. In diesem Fall wäre also this.MemberOfType
und MemberOfType
gleichwertig. Siehe auch base
.
Wenn eine Erweiterungsmethode für die aktuelle Instanz aufgerufen werden soll, ist this
erforderlich. Wenn Sie sich beispielsweise in einer nicht statischen Methode einer Klasse befinden, die IEnumerable<>
implementiert, und Sie die Erweiterung Count
von vor aufrufen möchten, müssen Sie IEnumerable<>
verwenden:
this.Count() // works like StaticClassForExtensionMethod.Count(this)
und this
kann dort nicht ausgelassen werden.
zum
Syntax: for (initializer; condition; iterator)
- Die
for
Schleife wird häufig verwendet, wenn die Anzahl der Iterationen bekannt ist. - Die Anweisungen im
initializer
werden nur einmal ausgeführt, bevor Sie in die Schleife gelangen. - Der
condition
enthält einen booleschen Ausdruck, der am Ende jeder Schleifeniteration ausgewertet wird, um zu bestimmen, ob die Schleife beendet oder erneut ausgeführt werden soll. - Der
iterator
Abschnitt definiert, was nach jeder Iteration des Schleifenkörpers passiert.
Dieses Beispiel zeigt, wie mit for
die Zeichen einer Zeichenfolge durchlaufen werden kann:
string str = "Hello";
for (int i = 0; i < str.Length; i++)
{
Console.WriteLine(str[i]);
}
Ausgabe:
H
e
l
l
O
Alle Ausdrücke, die eine for
-Anweisung definieren, sind optional. Die folgende Anweisung wird beispielsweise zum Erstellen einer Endlosschleife verwendet:
for( ; ; )
{
// Your code here
}
Der initializer
kann mehrere Variablen enthalten, sofern sie vom gleichen Typ sind. Der condition
kann aus einem beliebigen Ausdruck bestehen, der zu einem bool
ausgewertet werden kann. Der iterator
Abschnitt kann mehrere durch Kommas getrennte Aktionen ausführen:
string hello = "hello";
for (int i = 0, j = 1, k = 9; i < 3 && k > 0; i++, hello += i) {
Console.WriteLine(hello);
}
Ausgabe:
Hallo
hallo1
hallo12
während
Der while
Betreiber iteriert über einen Codeblock , bis die bedingte Abfrage gleich falsch oder der Code wird mit einer unterbrochen goto
, return
, break
oder throw
- Anweisung.
Syntax für while
Schlüsselwort:
while ( Bedingung ) { Codeblock; }
Beispiel:
int i = 0;
while (i++ < 5)
{
Console.WriteLine("While is on loop number {0}.", i);
}
Ausgabe:
"While ist auf Loop Nummer 1."
"While ist auf Loop Nummer 2."
"While ist auf Loop Nummer 3."
"While ist auf Loop Nummer 4."
"While ist auf Loop Nummer 5."
Eine while-Schleife ist Entry Controlled , da die Bedingung vor der Ausführung des eingeschlossenen Codeblocks geprüft wird. Dies bedeutet, dass die while-Schleife ihre Anweisungen nicht ausführen würde, wenn die Bedingung falsch ist.
bool a = false;
while (a == true)
{
Console.WriteLine("This will never be printed.");
}
Wenn Sie eine while
Bedingung angeben, ohne dass sie irgendwann falsch wird, führt dies zu einer Endlos- oder Endlosschleife. Dies sollte, soweit möglich, vermieden werden. Es können jedoch außergewöhnliche Umstände auftreten, wenn Sie dies benötigen.
Sie können eine solche Schleife wie folgt erstellen:
while (true)
{
//...
}
Beachten Sie, dass der C # -Compiler Schleifen wie transformiert
while (true)
{
// ...
}
oder
for(;;)
{
// ...
}
in
{
:label
// ...
goto label;
}
Beachten Sie, dass eine while-Schleife eine beliebige Bedingung haben kann, egal wie komplex sie ist, solange sie einen booleschen Wert (bool) auswertet (oder zurückgibt). Es kann auch eine Funktion enthalten, die einen booleschen Wert zurückgibt (da eine solche Funktion denselben Typ wie ein Ausdruck wie "a == x" auswertet). Zum Beispiel,
while (AgriculturalService.MoreCornToPick(myFarm.GetAddress()))
{
myFarm.PickCorn();
}
Rückkehr
MSDN: Die return-Anweisung beendet die Ausführung der Methode, in der sie erscheint, und gibt die Kontrolle an die aufrufende Methode zurück. Es kann auch einen optionalen Wert zurückgeben. Wenn die Methode ein ungültiger Typ ist, kann die return-Anweisung weggelassen werden.
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
}
im
Das in
Schlüsselwort hat drei Zwecke:
a) Als Teil der Syntax in einer foreach
Anweisung oder als Teil der Syntax in einer LINQ-Abfrage
foreach (var member in sequence)
{
// ...
}
b) Im Zusammenhang mit generischen Schnittstellen und generischen Delegattypen bedeutet dies eine Kontravarianz für den betreffenden Parameter Typ:
public interface IComparer<in T>
{
// ...
}
c) Im Kontext von LINQ bezieht sich die Abfrage auf die abgefragte Sammlung
var query = from x in source select new { x.Name, x.ID, };
mit
Es gibt zwei Arten der using
Schlüsselwörtern, using statement
und using directive
:
mit Anweisung :
Das Schlüsselwort
using
sorgt dafür, dass Objekte, die dieIDisposable
Schnittstelle implementieren, nach der Verwendung ordnungsgemäßIDisposable
werden. Es gibt ein separates Thema für die using-Anweisungunter Verwendung der Direktive
Die
using
Direktive hat drei Verwendungszwecke, die Using-Direktive finden Sie auf der Msdn-Seite . Es gibt ein separates Thema für die using-Direktive .
versiegelt
Bei der Anwendung auf eine Klasse verhindert der sealed
Modifikator, dass andere Klassen von ihr erben.
class A { }
sealed class B : A { }
class C : B { } //error : Cannot derive from the sealed class
Bei Verwendung auf eine virtual
Methode (oder virtuelle Eigenschaft) verhindert der sealed
Modifizierer, dass diese Methode (Eigenschaft) in abgeleiteten Klassen überschrieben wird .
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";
}
}
Größe von
Wird verwendet, um die Größe in Byte für einen nicht verwalteten Typ zu ermitteln
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
statisch
Mit dem static
Modifizierer wird ein statischer Member deklariert, der nicht instanziiert werden muss, um darauf zugegriffen zu werden, sondern wird einfach über seinen Namen, dh DateTime.Now
, DateTime.Now
.
static
kann mit Klassen, Feldern, Methoden, Eigenschaften, Operatoren, Ereignissen und Konstruktoren verwendet werden.
Während eine Instanz einer Klasse eine separate Kopie aller Instanzfelder der Klasse enthält, gibt es von jedem statischen Feld nur eine Kopie.
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
entspricht der Gesamtzahl der Instanzen der Klasse A
Der statische Modifikator kann auch verwendet werden, um einen statischen Konstruktor für eine Klasse zu deklarieren, statische Daten zu initialisieren oder Code auszuführen, der nur einmal aufgerufen werden muss. Statische Konstruktoren werden aufgerufen, bevor die Klasse zum ersten Mal referenziert wird.
class A
{
static public DateTime InitializationTime;
// Static constructor
static A()
{
InitializationTime = DateTime.Now;
// Guaranteed to only run once
Console.WriteLine(InitializationTime.ToString());
}
}
Eine static class
ist mit dem markierten static
Schlüsselwort, und kann für eine Reihe von Methoden als vorteilhaft Behälter verwendet werden , die auf Parametern arbeiten, erfordert jedoch nicht unbedingt auf eine Instanz gebunden zu sein. Aufgrund der static
Natur der Klasse kann sie nicht instanziiert werden, sie kann jedoch einen static constructor
. Einige Funktionen einer static class
umfassen:
- Kann nicht vererbt werden
- Kann nicht von etwas anderem als
Object
erben - Kann einen statischen Konstruktor enthalten, jedoch keinen Instanzkonstruktor
- Kann nur statische Member enthalten
- Ist versiegelt
Der Compiler ist auch freundlich und teilt dem Entwickler mit, ob Instanzmitglieder in der Klasse vorhanden sind. Ein Beispiel wäre eine statische Klasse, die zwischen US-amerikanischen und kanadischen Metriken konvertiert:
static class ConversionHelper {
private static double oneGallonPerLitreRate = 0.264172;
public static double litreToGallonConversion(int litres) {
return litres * oneGallonPerLitreRate;
}
}
Wenn Klassen als statisch deklariert werden:
public static class Functions
{
public static int Double(int value)
{
return value + value;
}
}
Alle Funktionen, Eigenschaften oder Member innerhalb der Klasse müssen ebenfalls als statisch deklariert werden. Es kann keine Instanz der Klasse erstellt werden. Im Wesentlichen können Sie mit einer statischen Klasse Funktionspakete erstellen, die logisch zusammengefasst sind.
Seit C # 6 kann auch static
verwendet using
, um statische Member und Methoden zu importieren. Sie können dann ohne Klassennamen verwendet werden.
Alte Art, ohne using static
:
using System;
public class ConsoleApplication
{
public static void Main()
{
Console.WriteLine("Hello World!"); //Writeline is method belonging to static class Console
}
}
Beispiel mit using static
using static System.Console;
public class ConsoleApplication
{
public static void Main()
{
WriteLine("Hello World!"); //Writeline is method belonging to static class Console
}
}
Nachteile
Statische Klassen können zwar unglaublich nützlich sein, haben jedoch ihre eigenen Vorbehalte:
Nach dem Aufruf der statischen Klasse wird die Klasse in den Arbeitsspeicher geladen und kann erst dann durch den Garbage Collector ausgeführt werden, wenn die AppDomain, in der sich die statische Klasse befindet, entladen wird.
Eine statische Klasse kann keine Schnittstelle implementieren.
int
int
ist ein Alias für System.Int32
, bei dem es sich um einen Datentyp für 32-Bit-Ganzzahlen mit System.Int32
handelt. Dieser Datentyp befindet sich in der mscorlib.dll
die jedes C # -Projekt beim Erstellen implizit verweist.
Bereich: -2.147.483.648 bis 2.147.483.647
int int1 = -10007;
var int2 = 2132012521;
lange
Das lange Schlüsselwort wird zur Darstellung von 64-Bit-Ganzzahlen mit Vorzeichen verwendet. Es ist ein Alias für den System.Int64
Datentyp in mscorlib.dll
, die implizit von jedem C # Projekt verwiesen wird , wenn Sie sie erstellen.
Jede lange Variable kann sowohl explizit als auch implizit deklariert werden:
long long1 = 9223372036854775806; // explicit declaration, long keyword used
var long2 = -9223372036854775806L; // implicit declaration, 'L' suffix used
Eine long- Variable kann einen beliebigen Wert zwischen –9.223.372.036.854.775.808 und 9.223.372.036.854.775.807 enthalten und kann in Situationen nützlich sein, in denen eine Variable einen Wert enthalten muss, der die Grenzen dessen überschreitet, was andere Variablen (z. B. die int- Variable) halten können.
ulong
Schlüsselwort für vorzeichenlose 64-Bit-Ganzzahlen. Es stellt System.UInt64
Datentyp gefunden mscorlib.dll
, die implizit von jedem C # Projekt verwiesen wird , wenn Sie sie erstellen.
Bereich: 0 bis 18.446.744.073.709.551.615
ulong veryLargeInt = 18446744073609451315;
var anotherVeryLargeInt = 15446744063609451315UL;
dynamisch
Das dynamic
Schlüsselwort wird mit dynamisch typisierten Objekten verwendet . Als dynamic
deklarierte Objekte verzichten auf statische Überprüfungen während der Kompilierung und werden stattdessen zur Laufzeit ausgewertet.
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
Im folgenden Beispiel wird dynamic
mit der Bibliothek Json.NET von Newtonsoft verwendet, um Daten leicht aus einer deserialisierten JSON-Datei lesen zu können.
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
}
Mit dem dynamischen Schlüsselwort sind einige Einschränkungen verbunden. Eine davon ist die Verwendung von Erweiterungsmethoden. Im folgenden Beispiel wird eine Erweiterungsmethode für string SayHello
: SayHello
.
static class StringExtensions
{
public static string SayHello(this string s) => $"Hello {s}!";
}
Der erste Ansatz besteht darin, es wie üblich (wie bei einer Zeichenfolge) aufzurufen:
var person = "Person";
Console.WriteLine(person.SayHello());
dynamic manager = "Manager";
Console.WriteLine(manager.SayHello()); // RuntimeBinderException
Kein Kompilierungsfehler, aber zur Laufzeit erhalten Sie eine RuntimeBinderException
. Um dieses Problem zu umgehen, rufen Sie die Erweiterungsmethode über die statische Klasse auf:
var helloManager = StringExtensions.SayHello(manager);
Console.WriteLine(helloManager);
virtuell, überschreiben, neu
virtuell und überschreiben
Mit dem virtual
Schlüsselwort können eine Methode, eine Eigenschaft, ein Indexer oder ein Ereignis von abgeleiteten Klassen überschrieben werden und polymorphes Verhalten darstellen. (Mitglieder sind in C # standardmäßig nicht virtuell.)
public class BaseClass
{
public virtual void Foo()
{
Console.WriteLine("Foo from BaseClass");
}
}
Um ein Element zu überschreiben, wird das override
in den abgeleiteten Klassen verwendet. (Beachten Sie, dass die Unterschrift der Mitglieder identisch sein muss.)
public class DerivedClass: BaseClass
{
public override void Foo()
{
Console.WriteLine("Foo from DerivedClass");
}
}
Das polymorphe Verhalten virtueller Member bedeutet, dass beim Aufruf das tatsächlich ausgeführte Member zur Laufzeit und nicht zur Kompilierzeit bestimmt wird. Das übergeordnete Element in der am meisten abgeleiteten Klasse, in der das betreffende Objekt eine Instanz ist, ist das ausgeführte Element.
Kurz gesagt, ein Objekt kann zur Kompilierzeit vom Typ BaseClass
deklariert werden. Wenn es sich jedoch zur Laufzeit um eine Instanz von DerivedClass
wird das überschriebene Member ausgeführt:
BaseClass obj1 = new BaseClass();
obj1.Foo(); //Outputs "Foo from BaseClass"
obj1 = new DerivedClass();
obj1.Foo(); //Outputs "Foo from DerivedClass"
Das Überschreiben einer Methode ist optional:
public class SecondDerivedClass: DerivedClass {}
var obj1 = new SecondDerivedClass();
obj1.Foo(); //Outputs "Foo from DerivedClass"
Neu
Da nur als virtual
definierte Member überschreibbar und polymorph sind, kann eine abgeleitete Klasse, die ein nicht virtuelles Member neu definiert, zu unerwarteten Ergebnissen führen.
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!
In diesem Fall wird das ausgeführte Member immer zur Kompilierzeit basierend auf dem Objekttyp bestimmt.
- Wenn das Objekt vom Typ
BaseClass
(auch wenn zur Laufzeit eine abgeleitete Klasse ist), wird die Methode vonBaseClass
ausgeführt - Wenn das Objekt vom Typ
DerivedClass
DerivedClass
ist, wird die Methode vonDerivedClass
ausgeführt.
Dies ist normalerweise ein Unfall (wenn ein Mitglied zum Basistyp hinzugefügt wird, nachdem ein identischer zum abgeleiteten Typ hinzugefügt wurde) und eine Compiler-Warnung CS0108 in diesen Szenarien generiert wird.
Wenn es beabsichtigt war, wird das new
Schlüsselwort verwendet, um die Warnung des Compilers zu unterdrücken (und andere Entwickler über Ihre Absichten zu informieren!). Das Verhalten bleibt gleich, das new
Schlüsselwort unterdrückt lediglich die Compiler-Warnung.
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!
Die Verwendung von Überschreiben ist nicht optional
Im Gegensatz zu C ++ ist die Verwendung des override
nicht optional:
public class A
{
public virtual void Foo()
{
}
}
public class B : A
{
public void Foo() // Generates CS0108
{
}
}
Das obige Beispiel führt auch CS0108 Warnung, weil B.Foo()
wird nicht automatisch überschrieben A.Foo()
. Fügen Sie override
wenn die Basisklasse überschrieben werden soll und polymorphes Verhalten verursacht wird. Fügen Sie new
wenn Sie nichtpolymorphes Verhalten wünschen, und lösen Sie den Aufruf mit dem statischen Typ auf. Letzteres sollte mit Vorsicht angewendet werden, da dies zu schweren Verwirrungen führen kann.
Der folgende Code führt sogar zu einem Fehler:
public class A
{
public void Foo()
{
}
}
public class B : A
{
public override void Foo() // Error: Nothing to override
{
}
}
Abgeleitete Klassen können Polymorphismus einführen
Der folgende Code ist vollkommen gültig (obwohl selten):
public class A
{
public void Foo()
{
Console.WriteLine("A");
}
}
public class B : A
{
public new virtual void Foo()
{
Console.WriteLine("B");
}
}
Alle Objekte mit einer statischen Referenz von B (und ihren Ableitungen) verwenden jetzt Polymorphismus, um Foo()
aufzulösen, während Referenzen von A 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";
Virtuelle Methoden können nicht privat sein
Der C # -Compiler verhindert strikt sinnlose Konstrukte. Als virtual
gekennzeichnete Methoden können nicht privat sein. Da eine private Methode von einem abgeleiteten Typ nicht gesehen werden kann, kann sie auch nicht überschrieben werden. Dies kann nicht kompiliert werden:
public class A
{
private virtual void Foo() // Error: virtual methods cannot be private
{
}
}
async, warte ab
Das await
Schlüsselwort wurde als Teil von C # 5.0 hinzugefügt, das ab Visual Studio 2012 unterstützt wird. Es nutzt die Task Parallel Library (TPL), die das Multithreading relativ vereinfacht. Die Schlüsselwörter async
und await
werden in der gleichen Funktion paarweise verwendet (siehe unten). Das await
Schlüsselwort wird verwendet, um die Ausführung der aktuellen asynchronen Methode anzuhalten, bis die erwartete asynchrone Task abgeschlossen ist und / oder ihre Ergebnisse zurückgegeben werden. Um das await
Schlüsselwort verwenden zu können, muss die Methode, die es verwendet, mit dem async
Schlüsselwort gekennzeichnet sein.
Mit async
mit void
wird dringend abgeraten. Für weitere Informationen können Sie hier nachschauen.
Beispiel:
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;
}
Ausgabe:
"Einen nutzlosen Prozess starten ..."
** ... 1 Sekunde Verzögerung ... **"Es dauerte 1000 Millisekunden, bis ein unbrauchbarer Prozess ausgeführt wurde."
Die Schlüsselwortpaare async
und await
können weggelassen werden, wenn eine Rückgabemethode Task
oder Task<T>
nur eine einzelne asynchrone Operation zurückgibt.
Lieber als das:
public async Task PrintAndDelayAsync(string message, int delay)
{
Debug.WriteLine(message);
await Task.Delay(x);
}
Es ist bevorzugt, dies zu tun:
public Task PrintAndDelayAsync(string message, int delay)
{
Debug.WriteLine(message);
return Task.Delay(x);
}
In C # 5.0 kann await
nicht in catch
und finally
.
Mit C # 6.0 await
kann verwendet werden , catch
und finally
.
verkohlen
Ein Zeichen ist ein einzelner Buchstabe, der in einer Variablen gespeichert ist. Es handelt sich um einen integrierten Werttyp, der zwei Byte Speicherplatz beansprucht. Es stellt den in mscorlib.dll
gefundenen System.Char
Datentyp dar, auf den von jedem C # -Projekt implizit verwiesen wird, wenn Sie sie erstellen.
Dafür gibt es mehrere Möglichkeiten.
-
char c = 'c';
-
char c = '\u0063'; //Unicode
-
char c = '\x0063'; //Hex
-
char c = (char)99;//Integral
Ein ushort, int, uint, long, ulong, float, double,
kann implizit in ushort, int, uint, long, ulong, float, double,
oder decimal
konvertiert werden und gibt den ganzzahligen Wert dieses char zurück.
ushort u = c;
gibt 99 usw. zurück
Es gibt jedoch keine impliziten Konvertierungen von anderen Typen in Zeichen. Stattdessen musst du sie werfen.
ushort u = 99;
char c = (char)u;
sperren
lock
bietet Thread-Sicherheit für einen Codeblock, sodass nur ein Thread innerhalb desselben Prozesses auf ihn zugreifen kann. Beispiel:
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
Anwendungsfälle:
Wann immer Sie über einen Codeblock verfügen, der Nebeneffekte erzeugen kann, wenn er von mehreren Threads gleichzeitig ausgeführt wird. Das Sperrschlüsselwort zusammen mit einem gemeinsam genutzten Synchronisationsobjekt (im Beispiel _objLock
) kann verwendet werden, um dies zu verhindern.
Beachten Sie, dass _objLock
nicht null
und mehrere Threads, die den Code ausführen, dieselbe Objektinstanz verwenden müssen (indem Sie sie entweder zu einem static
Feld machen oder für beide Threads dieselbe Klasseninstanz verwenden)
Auf der Compilerseite ist das Schlüsselwort lock ein syntaktischer Zucker, der durch Monitor.Enter(_lockObj);
und Monitor.Exit(_lockObj);
. Wenn Sie also die Sperre durch Umgeben des Codeblocks mit diesen beiden Methoden ersetzen, erhalten Sie dieselben Ergebnisse. Sie können den tatsächlichen Code in Syntactic Sugar im C # -Sperrbeispiel sehen
Null
Eine Variable eines Referenztyps kann entweder eine gültige Referenz auf eine Instanz oder eine Nullreferenz enthalten. Die Nullreferenz ist der Standardwert von Referenztypvariablen sowie von nullwertfähigen Werttypen.
null
ist das Schlüsselwort, das eine Nullreferenz darstellt.
Als Ausdruck kann es verwendet werden, um die Nullreferenz den Variablen der oben genannten Typen zuzuweisen:
object a = null;
string b = null;
int? c = null;
List<int> d = null;
Nicht-nullwertfähige Werttypen können keine Nullreferenz zugewiesen werden. Alle folgenden Zuweisungen sind ungültig:
int a = null;
float b = null;
decimal c = null;
Die Nullreferenz sollte nicht mit gültigen Instanzen verschiedener Typen verwechselt werden, z.
- eine leere Liste (
new List<int>()
) - eine leere Zeichenfolge (
""
) - die Zahl Null (
0
,0f
,0m
) - das Nullzeichen (
'\0'
)
Manchmal ist es sinnvoll zu prüfen, ob etwas entweder null oder ein leeres / Standardobjekt ist. Die System.String.IsNullOrEmpty (String) -Methode kann verwendet werden, um dies zu überprüfen, oder Sie können Ihre eigene gleichwertige Methode implementieren.
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 + "!");
}
}
intern
Das internal
Schlüsselwort ist ein Zugriffsmodifizierer für Typen und Typmember. Auf interne Typen oder Member kann nur innerhalb von Dateien in derselben Assembly zugegriffen werden
Verwendungszweck:
public class BaseClass
{
// Only accessible within the same assembly
internal static int x = 0;
}
Der Unterschied zwischen verschiedenen Zugriffsmodifizierern wird hier klargestellt
Zugriffsmodifikatoren
Öffentlichkeit
Auf den Typ oder das Member kann durch einen beliebigen anderen Code in derselben Assembly oder einer anderen Assembly, auf die sie verweist, zugegriffen werden.
Privatgelände
Auf den Typ oder Member kann nur durch Code in derselben Klasse oder Struktur zugegriffen werden.
geschützt
Auf den Typ oder Member kann nur durch Code in derselben Klasse oder Struktur oder in einer abgeleiteten Klasse zugegriffen werden.
intern
Auf den Typ oder das Member kann durch einen beliebigen Code in derselben Assembly zugegriffen werden, jedoch nicht von einer anderen Assembly.
intern geschützt
Auf den Typ oder das Member kann durch einen beliebigen Code in derselben Assembly oder durch eine abgeleitete Klasse in einer anderen Assembly zugegriffen werden.
Wenn kein Zugriffsmodifizierer festgelegt ist, wird ein Standard-Zugriffsmodifizierer verwendet. Es gibt also immer eine Form von Zugriffsmodifizierer, auch wenn sie nicht gesetzt ist.
woher
where
kann in C # zwei Zwecken dienen: Typeinschränkung in einem generischen Argument und Filtern von LINQ-Abfragen.
Betrachten wir in einer generischen Klasse
public class Cup<T>
{
// ...
}
T wird als Typparameter bezeichnet. Die Klassendefinition kann Einschränkungen für die tatsächlichen Typen auferlegen, die für T angegeben werden können.
Die folgenden Arten von Einschränkungen können angewendet werden:
- Werttyp
- Referenztyp
- Standardkonstruktor
- Vererbung und Umsetzung
Werttyp
In diesem Fall können nur struct
s (dazu gehören "primitive" Datentypen wie int
, boolean
usw.) angegeben werden
public class Cup<T> where T : struct
{
// ...
}
Referenztyp
In diesem Fall können nur Klassenarten geliefert werden
public class Cup<T> where T : class
{
// ...
}
Hybridwert / Referenztyp
Gelegentlich ist es wünschenswert, Typargumente auf die in einer Datenbank verfügbaren Argumente zu beschränken. Diese werden normalerweise Wertetypen und Zeichenfolgen zugeordnet. Da alle Typeinschränkungen erfüllt sein müssen, ist es nicht möglich anzugeben, where T : struct or string
(dies ist keine gültige Syntax). Eine Problemumgehung besteht darin, Typargumente auf IConvertible
zu beschränken, das die folgenden Typen enthält: "Boolean", "Boolean", "SByte", "Byte", "Int16", "IntInt16", "Int32", "Int32", "Int64", "Int64", "Int64", "Int64", "Single", "Double", Decimal, DateTime, Char und String. " Es ist möglich, dass andere Objekte IConvertible implementieren, obwohl dies in der Praxis selten ist.
public class Cup<T> where T : IConvertible
{
// ...
}
Standardkonstruktor
Es sind nur Typen zulässig, die einen Standardkonstruktor enthalten. Dazu gehören Werttypen und Klassen, die einen standardmäßigen (parameterlosen) Konstruktor enthalten
public class Cup<T> where T : new
{
// ...
}
Vererbung und Umsetzung
Es können nur Typen angegeben werden, die von einer bestimmten Basisklasse erben oder eine bestimmte Schnittstelle implementieren.
public class Cup<T> where T : Beverage
{
// ...
}
public class Cup<T> where T : IBeer
{
// ...
}
Die Einschränkung kann sogar auf einen anderen Typparameter verweisen:
public class Cup<T, U> where U : T
{
// ...
}
Für ein Typargument können mehrere Einschränkungen angegeben werden:
public class Cup<T> where T : class, new()
{
// ...
}
Die vorherigen Beispiele zeigen generische Einschränkungen für eine Klassendefinition. Einschränkungen können jedoch überall dort verwendet werden, wo ein Typargument angegeben wird: Klassen, Strukturen, Schnittstellen, Methoden usw.
where
kann auch eine LINQ-Klausel sein. In diesem Fall ist es analog zu WHERE
in 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
extern
Mit dem Schlüsselwort extern
Methoden deklariert, die extern implementiert werden. Dies kann in Verbindung mit dem Attribut DllImport verwendet werden, um über Interop-Services nicht verwalteten Code aufzurufen. was in diesem Fall mit static
Modifikator kommt
Zum Beispiel:
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);
}
}
Hierbei wird die SetForegroundWindow-Methode aus der User32.dll-Bibliothek importiert
Dies kann auch verwendet werden, um einen externen Assembly-Alias zu definieren. So können wir auf unterschiedliche Versionen derselben Komponenten aus einer Baugruppe verweisen.
Um auf zwei Assemblys mit denselben vollständig qualifizierten Typnamen zu verweisen, muss an der Eingabeaufforderung ein Alias wie folgt angegeben werden:
/r:GridV1=grid.dll
/r:GridV2=grid20.dll
Dadurch werden die externen Aliasnamen GridV1 und GridV2 erstellt. Um diese Aliasnamen innerhalb eines Programms zu verwenden, referenzieren Sie sie mit dem Schlüsselwort extern. Zum Beispiel:
extern alias GridV1;
extern alias GridV2;
bool
Schlüsselwort zum Speichern der booleschen Werte true
und false
. bool ist ein Alias von System.Boolean.
Der Standardwert eines bool ist false.
bool b; // default value is false
b = true; // true
b = ((5 + 2) == 6); // false
Damit ein Bool Nullwerte zulässt, muss er als Bool initialisiert werden.
Der Standardwert eines bool? ist Null.
bool? a // default value is null
wann
Das when
ist ein in C # 6 hinzugefügtes Schlüsselwort und wird für die Ausnahmefilterung verwendet.
Vor der Einführung des Schlüsselworts when
Sie für jeden Ausnahmetyp eine catch-Klausel haben können. Durch das Hinzufügen des Schlüsselworts ist jetzt eine feinere Kontrolle möglich.
A , when
Ausdruck ist mit einem befestigten catch
und nur dann , wenn die , when
Bedingung ist true
, der catch
wird Klausel ausgeführt werden. Es ist möglich, mehrere catch
Klauseln mit denselben Ausnahmeklassentypen und unterschiedlichen when
Bedingungen zu verwenden.
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);
ungeprüft
Das unchecked
Schlüsselwort verhindert, dass der Compiler nach Überläufen / Unterläufen sucht.
Zum Beispiel:
const int ConstantMax = int.MaxValue;
unchecked
{
int1 = 2147483647 + 10;
}
int1 = unchecked(ConstantMax + 10);
Ohne das unchecked
Schlüsselwort wird keine der beiden Additionsoperationen kompiliert.
Wann ist das nützlich?
Dies ist hilfreich, da dies die Beschleunigung von Berechnungen beschleunigen kann, die definitiv nicht überlaufen, da die Überprüfung des Überlaufs Zeit erfordert oder wenn ein Überlauf / Unterlauf gewünscht wird (z. B. beim Generieren eines Hashcodes).
Leere
Das reservierte Wort "void"
ist ein Alias des Typs "void"
System.Void
" und hat zwei Verwendungszwecke:
- Deklarieren Sie eine Methode ohne Rückgabewert:
public void DoSomething()
{
// Do some work, don't return any value to the caller.
}
Eine Methode mit einem Rückgabewert vom Typ void kann immer noch das Schlüsselwort return
in seinem Körper enthalten. Dies ist nützlich, wenn Sie die Ausführung der Methode beenden und den Fluss an den Aufrufer zurückgeben möchten:
public void DoSomething()
{
// Do some work...
if (condition)
return;
// Do some more work if the condition evaluated to false.
}
- Deklarieren Sie einen Zeiger auf einen unbekannten Typ in einem unsicheren Kontext.
In einem unsicheren Kontext kann ein Typ ein Zeigertyp, ein Werttyp oder ein Referenztyp sein. Eine int* myInt
ist normalerweise type* identifier
, wobei der Typ ein bekannter Typ ist - dh int* myInt
, kann aber auch void* identifier
, wobei der Typ unbekannt ist.
Beachten Sie, dass die Deklaration eines ungültigen Zeigertyps von Microsoft nicht empfohlen wird.
wenn, wenn ... sonst, wenn ... sonst wenn
Die if
Anweisung wird verwendet, um den Programmfluss zu steuern. Eine if
Anweisung gibt an, welche Anweisung basierend auf dem Wert eines Boolean
Ausdrucks ausgeführt werden soll.
Bei einer einzelnen Anweisung sind die braces
{} optional, werden jedoch empfohlen.
int a = 4;
if(a % 2 == 0)
{
Console.WriteLine("a contains an even number");
}
// output: "a contains an even number"
Die if
Klausel kann auch eine else
Klausel enthalten, die ausgeführt wird, wenn die Bedingung zu false ausgewertet wird:
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"
Mit dem if
... else if
-Konstrukt können Sie mehrere Bedingungen angeben:
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"
Beachten Sie, dass das Steuerelement andere Bedingungen überspringt, wenn eine Bedingung erfüllt ist, und springt zum Ende des jeweiligen if-Konstrukts. Die Reihenfolge der Tests ist daher wichtig, wenn Sie if .. else if-Konstrukt verwenden
Boolesche C # -Ausdrücke verwenden eine Kurzschlussbewertung . Dies ist wichtig in Fällen, in denen die Beurteilung der Bedingungen Nebenwirkungen haben kann:
if (someBooleanMethodWithSideEffects() && someOtherBooleanMethodWithSideEffects()) {
//...
}
Es kann nicht garantiert werden, dass someOtherBooleanMethodWithSideEffects
tatsächlich ausgeführt wird.
Dies ist auch in Fällen wichtig, in denen frühere Bedingungen gewährleisten, dass spätere Bedingungen "sicher" sind. Zum Beispiel:
if (someCollection != null && someCollection.Count > 0) {
// ..
}
Die Reihenfolge ist in diesem Fall sehr wichtig, weil, wenn wir die Reihenfolge umkehren:
if (someCollection.Count > 0 && someCollection != null) {
es wird ein Wurf NullReferenceException
, wenn someCollection
ist null
.
tun
Der do-Operator durchläuft einen Codeblock, bis eine bedingte Abfrage gleich false ist. Die do-while - Schleife kann auch durch eine unterbrochen werden goto
, return
, break
oder throw
Aussage.
Die Syntax für das do
Schlüsselwort lautet:
do { Codeblock; } while ( Bedingung );
Beispiel:
int i = 0;
do
{
Console.WriteLine("Do is on loop number {0}.", i);
} while (i++ < 5);
Ausgabe:
"Do ist in Loop Nummer 1."
"Do ist in Loop Nummer 2."
"Do ist in Loop Nummer 3."
"Do ist in Loop Nummer 4."
"Do ist in Loop Nummer 5."
Im Gegensatz zur while
Schleife ist die do-while-Schleife Exit Controlled . Dies bedeutet, dass die do-while-Schleife ihre Anweisungen mindestens einmal ausführt, selbst wenn die Bedingung beim ersten Mal fehlschlägt.
bool a = false;
do
{
Console.WriteLine("This will be printed once, even if a is false.");
} while (a == true);
Operator
Die meisten integrierten Operatoren (einschließlich Konvertierungsoperatoren) können mit dem Schlüsselwort operator
zusammen mit den Modifizierern public
und static
überladen werden.
Die Operatoren gibt es in drei Formen: unäre Operatoren, binäre Operatoren und Konvertierungsoperatoren.
Unäre und binäre Operatoren erfordern mindestens einen Parameter desselben Typs wie der enthaltende Typ, und einige erfordern einen zusätzlichen Übereinstimmungsoperator.
Konvertierungsoperatoren müssen in den oder den umgebenden Typ konvertieren.
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}}}";
}
Beispiel
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
Ein struct
ist ein Wertetyp, der normalerweise zum Einkapseln kleiner Gruppen verwandter Variablen verwendet wird, z. B. der Koordinaten eines Rechtecks oder der Merkmale eines Elements in einem Inventar.
Klassen sind Referenztypen, Strukturen sind Werttypen.
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();
}
}
}
Strukturen können auch Konstruktoren, Konstanten, Felder, Methoden, Eigenschaften, Indexer, Operatoren, Ereignisse und verschachtelte Typen enthalten. Wenn jedoch mehrere solcher Member erforderlich sind, sollten Sie in Betracht ziehen, Ihren Typ stattdessen als Klasse zu definieren.
Einige Vorschläge von MS, wann und wann Klassen verwendet werden sollen:
ERWÄGEN
Definieren einer Struktur anstelle einer Klasse, wenn Instanzen des Typs klein sind und häufig kurzlebig sind oder häufig in andere Objekte eingebettet sind.
VERMEIDEN
Definieren einer Struktur, sofern der Typ nicht alle der folgenden Merkmale aufweist:
- Es stellt logisch einen einzelnen Wert dar, ähnlich den primitiven Typen (int, double usw.).
- Es hat eine Instanzgröße unter 16 Byte.
- Es ist unveränderlich.
- Es muss nicht häufig verpackt werden.
Schalter
Die switch
Anweisung ist eine Steueranweisung, die einen aus einer Kandidatenliste auszuführenden Schalterabschnitt auswählt. Eine switch-Anweisung enthält einen oder mehrere switch-Abschnitte. Jeder Schalter Abschnitt enthält eine oder mehr case
Etiketten , gefolgt von einer oder mehrer Anweisungen. Wenn keine Fallbezeichnung einen übereinstimmenden Wert enthält, wird die Kontrolle an den default
, sofern vorhanden. Der Fallfall wird in C # streng genommen nicht unterstützt. Wenn 1 oder mehrere jedoch case
Etiketten leer sind, wird die Ausführung der Code des nächsten folgen case
Block, der Code enthält. Dies ermöglicht Gruppierung mehrerer case
Etiketten mit der gleichen Ausführung. Im folgende Beispiel, wenn month
12 entspricht, wird der Code in case 2
wird ausgeführt werden , da der case
Etikett 12
1
und 2
sind gruppiert. Wenn ein case
Block nicht leer ist, muss vor dem nächsten case
Label eine break
vorhanden sein, andernfalls weist der Compiler auf einen Fehler hin.
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;
}
Ein case
kann nur durch einen Wert gekennzeichnet werden zum Zeitpunkt der Kompilierung bekannt (zB 1
, "str"
, Enum.A
), so dass eine variable
ist kein gültiger case
Etikett, aber ein const
oder Enum
Wert (sowie jede wörtlicher Wert).
Schnittstelle
Eine interface
enthält die Signaturen von Methoden, Eigenschaften und Ereignissen. Die abgeleiteten Klassen definieren die Mitglieder, da das Interface nur die Deklaration der Mitglieder enthält.
Eine Schnittstelle wird mit dem interface
deklariert.
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); } }
}
unsicher
Das unsafe
Schlüsselwort kann in Typ- oder Methodendeklarationen oder zum Deklarieren eines Inline-Blocks verwendet werden.
Der Zweck dieses Schlüsselworts besteht darin, die Verwendung der unsicheren Teilmenge von C # für den betreffenden Block zu ermöglichen. Die unsichere Teilmenge enthält Funktionen wie Zeiger, Stapelzuordnung, C-artige Arrays usw.
Unsicherer Code ist nicht überprüfbar und wird daher nicht empfohlen. Das Kompilieren von unsicherem Code erfordert die Übergabe eines Wechsels an den C # -Compiler. Darüber hinaus erfordert die CLR, dass die ausgeführte Assembly volle Vertrauenswürdigkeit besitzt.
Trotz dieser Einschränkungen kann unsicherer Code dazu verwendet werden, einige Operationen performanter zu gestalten (z. B. Array-Indizierung) oder einfacher zu machen (z. B. Interop mit einigen nicht verwalteten Bibliotheken).
Als sehr einfaches Beispiel
// 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
}
}
Bei der Arbeit mit Zeigern können wir die Werte von Speicherorten direkt ändern, anstatt sie mit Namen ansprechen zu müssen. Beachten Sie, dass dies häufig die Verwendung des festen Schlüsselworts erfordert, um mögliche Speicherbeschädigung zu verhindern, da der Garbage Collector die Dinge in Bewegung setzt (andernfalls erhalten Sie den Fehler CS0212 ). Da eine Variable, die "fixiert" wurde, nicht geschrieben werden kann, muss häufig auch ein zweiter Zeiger vorhanden sein, der auf dieselbe Position wie der erste zeigt.
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++;"
}
}
}
Ausgabe:
1
4
9
16
25
36
49
64
81
100
unsafe
erlaubt auch die Verwendung von stackalloc, wodurch Speicherplatz wie _alloca in der C-Laufzeitbibliothek zugewiesen wird. Das obige Beispiel stackalloc
wie folgt stackalloc
, um stackalloc
zu verwenden:
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++;
}
(Ausgabe ist wie oben)
implizit
Das implicit
Schlüsselwort wird verwendet, um einen Konvertierungsoperator zu überladen. Sie können beispielsweise eine Fraction
Klasse deklarieren, die bei Bedarf automatisch in ein double
konvertiert werden soll und die automatisch aus int
konvertiert werden kann:
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);
}
}
wahr falsch
Die Schlüsselwörter true
und false
haben zwei Zwecke:
- Als wörtliche boolesche Werte
var myTrueBool = true;
var myFalseBool = false;
- Als Operatoren kann das überlastet werden
public static bool operator true(MyClass x)
{
return x.value >= 0;
}
public static bool operator false(MyClass x)
{
return x.value < 0;
}
Das Überladen des falschen Operators war vor C # 2.0 vor der Einführung von Nullable
Typen Nullable
.
Ein Typ, der den true
Operator überlädt, muss auch den false
Operator überladen.
Schnur
string
ist ein Alias für den .NET-Datentyp System.String
, der das System.String
Text (Zeichenfolgen) ermöglicht.
Notation:
string a = "Hello";
var b = "world";
var f = new string(new []{ 'h', 'i', '!' }); // hi!
Jedes Zeichen in der Zeichenfolge ist in UTF-16 codiert. Dies bedeutet, dass jedes Zeichen mindestens 2 Byte Speicherplatz benötigt.
ushort
Ein numerischer Typ, der zum Speichern von positiven 16-Bit-Ganzzahlen verwendet wird. ushort
ist ein Alias für System.UInt16
und belegt 2 Byte Speicher.
Gültiger Bereich ist 0
bis 65535
.
ushort a = 50; // 50
ushort b = 65536; // Error, cannot be converted
ushort c = unchecked((ushort)65536); // Overflows (wraps around to 0)
sbyte
Ein numerischer Typ zum Speichern von vorzeichenbehafteten 8-Bit-Ganzzahlen. sbyte
ist ein Alias für System.SByte
und belegt 1 Byte Speicherplatz. Verwenden Sie für das vorzeichenlose Äquivalent byte
.
Gültiger Bereich ist -127
bis 127
(der Rest wird zum Speichern des Zeichens verwendet).
sbyte a = 127; // 127
sbyte b = -127; // -127
sbyte c = 200; // Error, cannot be converted
sbyte d = unchecked((sbyte)129); // -127 (overflows)
var
Eine implizit typisierte lokale Variable, die stark typisiert ist, als ob der Benutzer den Typ deklariert hätte. Im Gegensatz zu anderen Variablendeklarationen bestimmt der Compiler den Variablentyp, den dieser darstellt, basierend auf dem ihm zugewiesenen Wert.
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).
Im Gegensatz zu anderen Variablentypen müssen Variablendefinitionen mit diesem Schlüsselwort bei der Deklaration initialisiert werden. Dies liegt daran, dass das Schlüsselwort var eine implizit typisierte Variable darstellt.
var i;
i = 10;
// This code will not run as it is not initialized upon declaration.
Mit dem Schlüsselwort var können auch neue Datentypen im laufenden Betrieb erstellt werden. Diese neuen Datentypen werden als anonyme Typen bezeichnet . Sie sind sehr nützlich, da sie es einem Benutzer ermöglichen, einen Satz von Eigenschaften zu definieren, ohne zuvor einen Objekttyp explizit deklarieren zu müssen.
Normaler anonymer Typ
var a = new { number = 1, text = "hi" };
LINQ-Abfrage, die einen anonymen Typ zurückgibt
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);
}
Sie können das var-Schlüsselwort in der foreach-Anweisung verwenden
public bool hasItemInList(List<String> list, string stringToSearch)
{
foreach(var item in list)
{
if( ( (string)item ).equals(stringToSearch) )
return true;
}
return false;
}
delegieren
Delegaten sind Typen, die einen Verweis auf eine Methode darstellen. Sie werden verwendet, um Methoden als Argumente an andere Methoden zu übergeben.
Delegierte können statische Methoden, Instanzmethoden, anonyme Methoden oder Lambda-Ausdrücke enthalten.
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;
}
}
Bei der Zuweisung einer Methode zu einem Delegierten ist zu beachten, dass die Methode denselben Rückgabetyp sowie dieselben Parameter aufweisen muss. Dies unterscheidet sich von einer "normalen" Methodenüberladung, bei der nur die Parameter die Signatur der Methode definieren.
Ereignisse werden auf Delegierten aufgebaut.
Veranstaltung
Ein event
ermöglicht es dem Entwickler, ein Benachrichtigungsmuster zu implementieren.
Einfaches Beispiel
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
}
}
teilweise
Das Schlüsselwort partial
kann während der Typdefinition von Klassen, Strukturen oder Schnittstellen verwendet werden, um die Typdefinition in mehrere Dateien aufzuteilen. Dies ist nützlich, um neue Funktionen in automatisch generierten Code zu integrieren.
File1.cs
namespace A
{
public partial class Test
{
public string Var1 {get;set;}
}
}
File2.cs
namespace A
{
public partial class Test
{
public string Var2 {get;set;}
}
}
Hinweis: Eine Klasse kann in beliebig viele Dateien aufgeteilt werden. Alle Deklarationen müssen sich jedoch unter demselben Namespace und derselben Assembly befinden.
Methoden können auch mit dem Schlüsselwort partial
deklariert werden. In diesem Fall enthält eine Datei nur die Methodendefinition und eine andere Datei die Implementierung.
Bei einer Teilmethode ist ihre Signatur in einem Teil eines Teiltyps definiert und ihre Implementierung in einem anderen Teil des Typs definiert. Mit partiellen Methoden können Klassenentwickler, ähnlich wie Ereignisbehandlungsroutinen, Methoden-Hooks bereitstellen, die Entwickler möglicherweise implementieren oder nicht. Wenn der Entwickler keine Implementierung bereitstellt, entfernt der Compiler die Signatur zur Kompilierzeit. Für Teilmethoden gelten folgende Bedingungen:
- Unterschriften in beiden Teilen des Teiltyps müssen übereinstimmen.
- Die Methode muss ungültig sein.
- Es sind keine Zugriffsmodifizierer zulässig. Teilmethoden sind implizit privat.
- MSDN
File1.cs
namespace A
{
public partial class Test
{
public string Var1 {get;set;}
public partial Method1(string str);
}
}
File2.cs
namespace A
{
public partial class Test
{
public string Var2 {get;set;}
public partial Method1(string str)
{
Console.WriteLine(str);
}
}
}
Anmerkung: Der Typ, der die partielle Methode enthält, muss auch als partiell deklariert werden.