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.

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()
{
    volatile int 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 und bool
  • Aufzählungstypen basierend auf byte , sbyte , short , ushort , int oder uint
  • IntPtr und UIntPtr

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 wie Interlocked.Read und Interlocked.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);

Live-Demo zu .NET-Geige

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:

7,0
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

Live-Demo zu .NET-Geige

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

Live-Demo zu .NET-Geige

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;
}

Live-Demo zu .NET-Geige

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;
}

Live-Demo zu .NET-Geige

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
}

Live-Demo zu .NET-Geige

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;
}

Live-Demo zu .NET-Geige

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

Live-Demo zu .NET-Geige

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, 

Live-Demo zu .NET-Geige

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.");
}

Live-Demo zu .NET-Geige

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");
}

Live-Demo zu .NET-Geige

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

Live-Demo zu .NET-Geige

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

Live-Demo zu .NET-Geige

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

Live-Demo zu .NET-Geige

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 :

  1. mit Anweisung :

    Das Schlüsselwort using sorgt dafür, dass Objekte, die die IDisposable Schnittstelle implementieren, nach der Verwendung ordnungsgemäß IDisposable werden. Es gibt ein separates Thema für die using-Anweisung

  2. unter 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 von BaseClass ausgeführt
  • Wenn das Objekt vom Typ DerivedClass DerivedClass ist, wird die Methode von DerivedClass 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);
}
5,0

In C # 5.0 kann await nicht in catch und finally .

6,0

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.

  1. char c = 'c';
  2. char c = '\u0063'; //Unicode
  3. char c = '\x0063'; //Hex
  4. 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:

  1. 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.
}
  1. 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:

  1. Als wörtliche boolesche Werte
var myTrueBool = true;
var myFalseBool = false;
  1. 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
    }
}

MSDN-Referenz

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.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow