Ricerca…


introduzione

Le parole chiave sono identificatori predefiniti e riservati con un significato speciale per il compilatore. Non possono essere usati come identificatori nel tuo programma senza il prefisso @ . Ad esempio, @if è un identificatore legale ma non la parola chiave if .

Osservazioni

C # ha una collezione predefinita di "parole chiave" (o parole riservate) che hanno ciascuna una funzione speciale. Queste parole non possono essere utilizzate come identificatori (nomi per variabili, metodi, classi, ecc.) A meno che non siano preceduti da @ .

Oltre a questi, C # utilizza anche alcune parole chiave per fornire un significato specifico nel codice. Sono chiamati parole chiave contestuali. Le parole chiave contestuali possono essere utilizzate come identificatori e non devono essere precedute da prefisso @ quando vengono utilizzate come identificatori.

stackalloc

La parola chiave stackalloc crea una regione di memoria nello stack e restituisce un puntatore all'inizio di tale memoria. La memoria allocata nello stack viene automaticamente rimossa quando viene chiuso l'ambito in cui è stato creato.

//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;
...

Utilizzato in un contesto non sicuro.

Come con tutti i puntatori in C # non ci sono limiti di controllo su letture e compiti. La lettura oltre i limiti della memoria allocata avrà risultati imprevedibili - potrebbe accedere a qualche posizione arbitraria all'interno della memoria o potrebbe causare un'eccezione di violazione di accesso.

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

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

La memoria allocata nello stack viene automaticamente rimossa quando viene chiuso l'ambito in cui è stato creato. Ciò significa che non si dovrebbe mai restituire la memoria creata con stackalloc o conservarla oltre la durata dell'ambito.

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 può essere usato solo quando si dichiarano e si inizializzano variabili. Quanto segue non è valido:

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

Osservazioni:

stackalloc deve essere usato solo per ottimizzare le prestazioni (sia per il calcolo che per l'interoperabilità). Ciò è dovuto al fatto che:

  • Il garbage collector non è necessario in quanto la memoria viene allocata nello stack piuttosto che nell'heap - la memoria viene rilasciata non appena la variabile esce dallo scope
  • È più veloce allocare memoria nello stack anziché nell'heap
  • Aumentare la possibilità di colpi di cache sulla CPU a causa della località dei dati

volatile

L'aggiunta della parola chiave volatile a un campo indica al compilatore che il valore del campo può essere modificato da più thread separati. Lo scopo principale della parola chiave volatile è impedire le ottimizzazioni del compilatore che presuppongono solo l'accesso a thread singolo. L'utilizzo di volatile garantisce che il valore del campo sia il valore più recente disponibile e che il valore non sia soggetto alla memorizzazione nella cache dei valori non volatili.

È buona norma contrassegnare tutte le variabili che possono essere utilizzate da più thread come volatile per prevenire comportamenti imprevisti a causa di ottimizzazioni dietro le quinte. Considera il seguente blocco di codice:

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

Nel suddetto codice, il compilatore legge le istruzioni x = 5 y = x + 10 e determina che il valore di y finirà sempre per 15. Quindi, ottimizzerà l'ultima istruzione come y = 15 . Tuttavia, la variabile x è in realtà un campo public e il valore di x può essere modificato in fase di esecuzione attraverso un thread diverso che agisce su questo campo separatamente. Ora considera questo blocco di codice modificato. Si noti che il campo x è ora dichiarato come volatile .

public class Example
{
    public volatile int x;

    public void DoStuff()
    {
        x = 5;

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

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

Ora, il compilatore cerca gli usi di lettura del campo x e assicura che il valore corrente del campo sia sempre recuperato. Ciò garantisce che anche se più thread stanno leggendo e scrivendo in questo campo, il valore corrente di x viene sempre recuperato.

volatile può essere utilizzato solo su campi all'interno di class o struct . Quanto segue non è valido :

public void MyMethod()
{
    volatile int x;
}

volatile può essere applicato solo ai campi dei seguenti tipi:

  • tipi di riferimento o parametri di tipo generico noti per essere tipi di riferimento
  • tipi primitivi come sbyte , byte , short , ushort , int , uint , char , float e bool
  • tipi di enumerazione basati su byte , sbyte , short , ushort , int o uint
  • IntPtr e UIntPtr

Osservazioni:

  • Il modificatore volatile viene in genere utilizzato per un campo a cui si accede da più thread senza utilizzare l'istruzione lock per serializzare l'accesso.
  • La parola chiave volatile può essere applicata a campi di tipi di riferimento
  • La parola chiave volatile non renderà operativo su primitive a 64 bit su una piattaforma atomica a 32 bit. Le operazioni interbloccate come Interlocked.Read e Interlocked.Exchange devono ancora essere utilizzate per un accesso multi-thread sicuro su queste piattaforme.

fisso

L'istruzione fissa corregge la memoria in un'unica posizione. Gli oggetti in memoria di solito si spostano, questo rende possibile la raccolta dei dati inutili. Ma quando usiamo i puntatori non sicuri agli indirizzi di memoria, quella memoria non deve essere spostata.

  • Usiamo l'istruzione fixed per assicurare che il garbage collector non rilasci i dati di stringa.

Risolto Variabili

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

Utilizzato in un contesto non sicuro.

Fixed Array Size

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

fixed può essere usato solo sui campi di una struct (deve essere usato anche in un contesto non sicuro).

predefinito

Per classi, interfacce, delegate, array, default(TheType) null (come int?) E tipi di puntatore, il default(TheType) restituisce null :

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

Per le strutture e le enumerazioni, il default(TheType) restituisce lo stesso del 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) può essere particolarmente utile quando T è un parametro generico per il quale non è presente alcun vincolo per decidere se T è un tipo di riferimento o un tipo di valore, ad esempio:

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

sola lettura

La parola chiave readonly è un modificatore di campo. Quando una dichiarazione di campo include un modificatore di readonly , le assegnazioni a quel campo possono avvenire solo come parte della dichiarazione o in un costruttore della stessa classe.

La parola chiave readonly è diversa dalla parola chiave const . Un campo const può essere inizializzato solo alla dichiarazione del campo. Un campo di readonly può essere inizializzato o alla dichiarazione o in un costruttore. Pertanto, i campi di readonly possono avere valori diversi a seconda del costruttore utilizzato.

La parola chiave readonly viene spesso utilizzata quando si inseriscono le dipendenze.

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

Nota: la dichiarazione di un campo in sola lettura non implica l' immutabilità . Se il campo è un tipo di riferimento, è possibile modificare il contenuto dell'oggetto. In sola lettura viene in genere utilizzato per impedire che l'oggetto venga sovrascritto e assegnato solo durante l' istanziazione di tale oggetto.

Nota: all'interno del costruttore è possibile riassegnare un campo di sola lettura

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

//In code

private readonly Car car = new Car();

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

come

La parola chiave as è un operatore simile a un cast . Se un cast non è possibile, l'utilizzo di as produce null che risultare in InvalidCastException .

expression as type è equivalente a expression is type ? (type)expression : (type)null con l'avvertenza che as è valido solo alle conversioni di riferimento, conversioni Null e conversioni boxe. Le conversioni definite dall'utente non sono supportate; al suo posto deve essere usato un cast regolare.

Per l'espansione di cui sopra, il compilatore genera codice tale che l' expression sarà valutata solo una volta e utilizzerà un controllo di tipo dinamico singolo (a differenza dei due nell'esempio sopra riportato).

as può essere utile quando si aspetta un argomento per facilitare diversi tipi. In particolare, concede all'utente più opzioni - piuttosto che controllare ogni possibilità con is prima del casting, o semplicemente lanciare e catturare le eccezioni. È consigliabile utilizzare "come" quando si esegue il casting / controllo di un oggetto che causerà solo una penalità di annullamento. L'utilizzo is di controllare, quindi il cast causerà due penalità di annullamento.

Se un argomento dovrebbe essere un'istanza di un tipo specifico, è preferibile un cast regolare in quanto il suo scopo è più chiaro per il lettore.

Poiché una chiamata a as può produrre null , controllare sempre il risultato di evitare un NullReferenceException .

Esempio di utilizzo

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 su .NET Fiddle

Esempio equivalente senza utilizzo as :

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

Ciò è utile quando si esegue l'override della funzione Equals nelle classi personalizzate.

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
    }

}

è

Verifica se un oggetto è compatibile con un determinato tipo, cioè se un oggetto è un'istanza del tipo BaseInterface o un tipo che deriva da 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

Se l'intento del cast è quello di utilizzare l'oggetto, è buona norma utilizzare il as parola chiave'

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
}

Tuttavia, dalla caratteristica di pattern matching C # 7 si estende l'operatore is per controllare un tipo e dichiarare una nuova variabile allo stesso tempo. La stessa parte di codice con C # 7:

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

tipo di

Restituisce il Type di un oggetto, senza bisogno di istanziarlo.

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 è usato per rappresentare valori che non cambieranno mai per tutta la durata del programma. Il suo valore è costante dalla fase di compilazione , a differenza della parola chiave readonly , il cui valore è costante dal tempo di esecuzione.

Ad esempio, poiché la velocità della luce non cambierà mai, possiamo memorizzarla in una costante.

const double c = 299792458;  // Speed of light

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

Questo è essenzialmente uguale alla return mass * 299792458 * 299792458 , in quanto il compilatore sostituirà direttamente c con il suo valore costante.

Di conseguenza, c non può essere modificato una volta dichiarato. Quanto segue produrrà un errore in fase di compilazione:

const double c = 299792458;  // Speed of light 

c = 500;  //compile-time error

Una costante può essere preceduta dagli stessi modificatori di accesso dei metodi:

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

const membri const sono static per natura. Tuttavia, l'uso static esplicito non è permesso.

È inoltre possibile definire le costanti del metodo locale:

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

Questi non possono essere preceduti da una parola chiave private o public , poiché sono implicitamente locali rispetto al metodo in cui sono definiti.


Non tutti i tipi possono essere utilizzati in una dichiarazione const . I tipi di valori consentiti sono i tipi predefiniti sbyte , byte , short , ushort , int , uint , long , ulong , char , float , double , decimal , bool e tutti i tipi enum . Provare a dichiarare membri const con altri tipi di valore (come TimeSpan o Guid ) fallirà in fase di compilazione.

Per la speciale string tipo di riferimento predefinita, le costanti possono essere dichiarate con qualsiasi valore. Per tutti gli altri tipi di riferimento, le costanti possono essere dichiarate ma devono sempre avere il valore null .


Poiché i valori const sono noti in fase di compilazione, sono consentiti come etichette case in un'istruzione switch , come argomenti standard per parametri facoltativi, come argomenti per l'attribuzione di specifiche e così via.


Se i valori const vengono utilizzati tra diversi assembly, è necessario prestare attenzione con il controllo delle versioni. Ad esempio, se l'assembly A definisce un public const int MaxRetries = 3; e l'assembly B usa quella costante, quindi se il valore di MaxRetries viene in seguito modificato in 5 nell'assembly A (che viene quindi ricompilato), tale modifica non sarà effettiva nell'assembly B a meno che l' assembly B non venga ricompilato (con un riferimento alla nuova versione di A).

Per questo motivo, se un valore può cambiare nelle revisioni future del programma e se il valore deve essere visibile pubblicamente, non dichiarare tale valore const se non si è certi che tutti gli assembly dipendenti verranno ricompilati ogni volta che qualcosa viene modificato. L'alternativa sta usando static readonly invece di const , che viene risolto in fase di runtime.

namespace

La parola chiave namespace è un costrutto organizzativo che ci aiuta a capire come è organizzato un codebase. I namespace in C # sono spazi virtuali piuttosto che essere in una cartella fisica.

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

I namespace in C # possono anche essere scritti in sintassi concatenata. Quanto segue è equivalente a sopra:

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

prova, prendi, finalmente, lancia

try , catch , finally e throw ti permettono di gestire le eccezioni nel tuo codice.

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

Nota: la parola chiave return può essere utilizzata nel blocco try e il blocco finally verrà comunque eseguito (appena prima di tornare). Per esempio:

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

L'istruzione connection.Close() verrà eseguita prima del risultato della connection.Get(query) viene restituito.

Continua

Passa immediatamente il controllo alla successiva iterazione del costrutto del loop che lo racchiude (for, foreach, do, while):

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

Produzione:

5
6
7
8
9

Live Demo su .NET Fiddle

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

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

Produzione:

un
B
c
d

Live Demo su .NET Fiddle

ref, fuori

Le parole chiave ref e out sì che un argomento venga passato per riferimento, non per valore. Per i tipi di valore, ciò significa che il valore della variabile può essere modificato dal destinatario.

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

Per i tipi di riferimento, l'istanza nella variabile non può essere modificata solo (come nel caso senza ref ), ma può anche essere sostituita del tutto:

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

La principale differenza tra la parola chiave out e ref è che ref richiede che la variabile venga inizializzata dal chiamante, mentre out passa tale responsabilità al destinatario.

Per utilizzare un parametro out , sia la definizione del metodo sia il metodo di chiamata devono utilizzare esplicitamente la parola chiave out .

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

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

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

Live Demo su .NET Fiddle

Quanto segue non si compila, perché out parametri out devono avere un valore assegnato prima che il metodo ritorni (si compilerebbe invece usando ref ):

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

usando la parola chiave come modificatore generico

out parola chiave out può essere utilizzata anche nei parametri di tipo generico quando si definiscono interfacce e delegati generici. In questo caso, la parola chiave out specifica che il parametro type è covariante.

La covarianza consente di utilizzare un tipo più derivato rispetto a quello specificato dal parametro generico. Ciò consente la conversione implicita di classi che implementano interfacce varianti e la conversione implicita di tipi di delegati. Covarianza e controvarianza sono supportate per i tipi di riferimento, ma non sono supportate per i tipi di valore. - 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

selezionato, deselezionato

Le parole chiave checked e unchecked definiscono come le operazioni gestiscono l'overflow matematico. "Overflow" nel contesto delle parole chiave checked e unchecked si verifica quando un'operazione di aritmetica intera dà come risultato un valore maggiore di grandezza rispetto a quello che il tipo di dati di destinazione può rappresentare.

Quando si verifica un overflow all'interno di un blocco checked (o quando il compilatore è impostato per utilizzare globalmente l'aritmetica controllata), viene lanciata un'eccezione per avvisare di un comportamento indesiderato. Nel frattempo, in un blocco unchecked , l'overflow è silenzioso: non vengono generate eccezioni e il valore si limita semplicemente al limite opposto. Questo può portare a bug sottili e difficili da trovare.

Dal momento che la maggior parte delle operazioni aritmetiche vengono eseguite su valori che non sono grandi o abbastanza piccoli da eccedere, la maggior parte delle volte non è necessario definire esplicitamente un blocco come checked . È necessario prestare attenzione quando si eseguono operazioni aritmetiche su input non limitati che possono causare un overflow, ad esempio quando si esegue l'aritmetica in funzioni ricorsive o mentre si immette l'input dell'utente.

checkedunchecked influiscono sulle operazioni aritmetiche in virgola mobile.

Quando un blocco o un'espressione viene dichiarato come unchecked , qualsiasi operazione aritmetica al suo interno può eccedere senza causare errori. Un esempio in cui questo comportamento è desiderato potrebbe essere il calcolo di un checksum, in cui il valore è consentito per "avvolgere" durante il calcolo:

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

Uno degli usi più comuni per unchecked è l'implementazione di una sovrascrittura personalizzata per object.GetHashCode() , un tipo di checksum. Puoi vedere l'uso della parola chiave nelle risposte a questa domanda: qual è il miglior algoritmo per un System.Object.GetHashCode sottoposto a override? .

Quando un blocco o un'espressione viene dichiarata come checked , qualsiasi operazione aritmetica che provoca un overflow genera una OverflowException generata.

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

Sia selezionato che deselezionato possono essere in forma di blocco ed espressione.

I blocchi selezionati e deselezionati non influiscono sui metodi chiamati, solo gli operatori chiamati direttamente nel metodo corrente. Ad esempio, Enum.ToObject() , Convert.ToInt32() e gli operatori definiti dall'utente non sono interessati dai contesti personalizzati selezionati / non selezionati.

Nota : il comportamento predefinito di overflow predefinito (selezionato o deselezionato) può essere modificato nelle proprietà del progetto o tramite l' opzione della riga di comando / controllata [+ | -] . È normale eseguire il controllo delle operazioni di debug e deselezionare le build di rilascio. Le parole chiave checked e unchecked verranno quindi utilizzate solo laddove non si applica un approccio predefinito e per garantire la correttezza è necessario un comportamento esplicito.

vai a

goto può essere utilizzato per passare a una riga specifica all'interno del codice, specificata da un'etichetta.

goto come:

Etichetta:

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

Live Demo su .NET Fiddle

Caso clinico:

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 su .NET Fiddle

Ciò è particolarmente utile nell'esecuzione di più comportamenti in un'istruzione switch, in quanto C # non supporta i blocchi di casi fall-through .

Eccezione Riprova

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

Live Demo su .NET Fiddle

Simile a molte lingue, l'uso della parola chiave goto è sconsigliato tranne i casi di seguito.

Validi usi di goto che si applicano a C #:

  • Caso fall-through nell'istruzione switch.

  • Pausa multi-livello LINQ può essere usato spesso, ma di solito ha prestazioni peggiori.

  • Deallocazione delle risorse quando si lavora con oggetti di basso livello non aperti. In C #, gli oggetti di basso livello dovrebbero essere normalmente racchiusi in classi separate.

  • Macchine a stati finiti, ad esempio parser; utilizzato internamente dal compilatore generato async / await state machine.

enum

La parola chiave enum dice al compilatore che questa classe eredita dalla classe astratta Enum , senza che il programmatore debba ereditarlo esplicitamente. Enum è un discendente di ValueType , che è inteso per l'uso con un insieme distinto di costanti con nome.

public enum DaysOfWeek
{
    Monday,
    Tuesday,
}

Puoi facoltativamente specificare un valore specifico per ognuno (o alcuni di essi):

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

In questo esempio ho omesso un valore per 0, di solito è una cattiva pratica. Un enum avrà sempre un valore predefinito prodotto dalla conversione esplicita (YourEnumType) 0 , dove YourEnumType è il tipo enume dichiarato. Senza un valore di 0 definito, un enum non avrà un valore definito all'avvio.

Il tipo di enum predefinito di base è int , è possibile modificare il tipo sottostante a qualsiasi tipo integrale incluso byte , sbyte , short , ushort , int , uint , long e ulong . Di seguito è riportato un enum con byte tipo sottostante:

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

Si noti inoltre che è possibile convertire in / dal tipo sottostante semplicemente con un cast:

int value = (int)NotableYear.EndOfWwI;

Per questi motivi è meglio controllare sempre se un enum è valido quando si espongono le funzioni della libreria:

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

    // ...
}

base

La parola chiave di base viene utilizzata per accedere ai membri da una classe base. Viene comunemente utilizzato per chiamare le implementazioni di base dei metodi virtuali o per specificare quale costruttore di base deve essere chiamato.

Scegliere un costruttore

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

Chiamare l'implementazione di base del metodo virtuale

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

È possibile utilizzare la parola chiave di base per chiamare un'implementazione di base da qualsiasi metodo. Ciò lega la chiamata del metodo direttamente all'implementazione di base, il che significa che anche se le nuove classi secondarie sostituiscono un metodo virtuale, l'implementazione di base verrà comunque chiamata, quindi è necessario utilizzarla con cautela.

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

per ciascuno

foreach viene utilizzato per scorrere gli elementi di una matrice o gli elementi all'interno di una raccolta che implementa IEnumerable ✝.

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

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

Questo uscirà

"Ciao mondo!"
"Come stai oggi?"
"Addio"

Live Demo su .NET Fiddle

È possibile uscire dal ciclo foreach in qualsiasi momento utilizzando la parola chiave break o passare alla successiva iterazione utilizzando la parola chiave continue .

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 su .NET Fiddle

Si noti che l'ordine di iterazione è garantito solo per determinate raccolte come matrici ed List , ma non è garantito per molte altre raccolte.


✝ Mentre IEnumerable viene in genere utilizzato per indicare le raccolte enumerabili, foreach richiede solo che la raccolta esponga pubblicamente il metodo object GetEnumerator() , che deve restituire un oggetto che espone il metodo bool MoveNext() e l' object Current { get; } proprietà.

params

params consente a un parametro del metodo di ricevere un numero variabile di argomenti, vale a dire zero, uno o più argomenti sono consentiti per quel parametro.

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

Questo metodo può ora essere chiamato con una lista tipica di argomenti int , o una matrice di interi.

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

params deve apparire al massimo una volta e se usato, deve essere l' ultimo nella lista degli argomenti, anche se il tipo successivo è diverso da quello dell'array.


Fare attenzione quando si sovraccaricano le funzioni quando si utilizza la parola chiave params . C # preferisce abbinare sovraccarichi più specifici prima di ricorrere al tentativo di usare sovraccarichi con params . Ad esempio se hai due metodi:

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

Quindi lo specifico sovraccarico di 2 argomenti avrà la precedenza prima di provare il sovraccarico dei params .

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)

rompere

In un ciclo (for, foreach, do, while) l'istruzione break interrompe l'esecuzione del ciclo più interno e ritorna al codice dopo di esso. Inoltre può essere utilizzato con yield in cui specifica che un iteratore è giunto al termine.

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 su .NET Fiddle

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

L'istruzione break viene anche utilizzata nei costrutti del caso di commutazione per uscire da un caso o da un segmento predefinito.

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

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

Nelle istruzioni switch, la parola chiave 'break' è richiesta alla fine di ogni dichiarazione di caso. Ciò è contrario ad alcune lingue che consentono di "passare attraverso" alla successiva dichiarazione del caso nella serie. I rimedi per questo includevano le istruzioni "goto" o l'impilamento sequenziale delle dichiarazioni "case".

Il codice seguente darà i numeri 0, 1, 2, ..., 9 e l'ultima riga non verrà eseguita. yield break indica la fine della funzione (non solo un loop).

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 su .NET Fiddle

Nota che a differenza di altri linguaggi, non c'è modo di etichettare una particolare interruzione in C #. Ciò significa che nel caso di cicli annidati, verrà interrotto solo il ciclo più interno:

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

Se vuoi uscire dal ciclo esterno qui, puoi utilizzare una delle diverse strategie, ad esempio:

  • Una dichiarazione goto per saltare fuori dall'intera struttura del ciclo.
  • Una specifica variabile di flag ( shouldBreak nell'esempio seguente) che può essere verificata alla fine di ogni iterazione del ciclo esterno.
  • Rifattorizzare il codice per utilizzare un'istruzione return nel corpo del ciclo più interno o evitare del tutto l'intera struttura del ciclo annidato.
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

astratto

Una classe contrassegnata con la parola chiave abstract non può essere istanziata.

Una classe deve essere contrassegnata come astratta se contiene membri astratti o se eredita membri astratti che non implementa. Una classe può essere contrassegnata come astratta anche se non sono coinvolti membri astratti.

Le classi astratte vengono solitamente utilizzate come classi base quando alcune parti dell'implementazione devono essere specificate da un altro componente.

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

Un metodo, una proprietà o un evento contrassegnati con la parola chiave abstract indica che l'implementazione per tale membro dovrebbe essere fornita in una sottoclasse. Come accennato in precedenza, i membri astratti possono apparire solo in classi astratte.

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, double, decimal

galleggiante

float è un alias per il tipo di dati .NET System.Single . Consente di memorizzare i numeri in virgola mobile a precisione singola IEEE 754. Questo tipo di dati è presente in mscorlib.dll cui fa riferimento implicitamente ogni progetto C # quando vengono creati.

Intervallo approssimativo: da -3,4 × 10 38 a 3,4 × 10 38

Precisione decimale: 6-9 cifre significative

Notazione :

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

Va notato che il tipo float spesso causa errori di arrotondamento significativi. Nelle applicazioni in cui la precisione è importante, devono essere considerati altri tipi di dati.


Doppio

double è un alias del tipo di dati .NET System.Double . Rappresenta un numero a virgola mobile a 64 bit a doppia precisione. Questo tipo di dati è presente in mscorlib.dll cui viene fatto implicitamente riferimento in qualsiasi progetto C #.

Intervallo: ± 5,0 × 10 -324 a ± 1,7 × 10 308

Precisione decimale: 15-16 cifre significative

Notazione :

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

decimale

decimal è un alias del tipo di dati .NET System.Decimal . Rappresenta una parola chiave indica un tipo di dati a 128 bit. Rispetto ai tipi a virgola mobile, il tipo decimale ha più precisione e un intervallo più piccolo, il che lo rende appropriato per i calcoli finanziari e monetari. Questo tipo di dati è presente in mscorlib.dll cui viene fatto implicitamente riferimento in qualsiasi progetto C #.

Intervallo: da -7,9 × 10 28 a 7,9 × 10 28

Precisione decimale: 28-29 cifre significative

Notazione :

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

uint

Un intero senza segno , o uint , è un tipo di dati numerico che può contenere solo interi positivi. Come suggerisce il nome, rappresenta un numero intero a 32 bit senza segno. La stessa parola chiave uint è un alias per il tipo di sistema di tipo comune System.UInt32 . Questo tipo di dati è presente in mscorlib.dll , a cui fa riferimento implicitamente ogni progetto C # quando vengono creati. Occupa quattro byte di spazio di memoria.

Gli interi senza segno possono contenere qualsiasi valore compreso tra 0 e 4.294.967.295.

Esempi su come e ora non dichiarare numeri interi senza segno

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

Nota: secondo Microsoft , si consiglia di utilizzare il tipo di dati int ove possibile in quanto il tipo di dati uint non è conforme a CLS.

Questo

Il this parola chiave si riferisce all'istanza corrente di classe (oggetto). In questo modo si possono distinguere due variabili con lo stesso nome, una a livello di classe (un campo) e una essendo un parametro (o una variabile locale) di un metodo.

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

Altri usi della parola chiave sono concatenamento di sovraccarichi di costruttori non statici :

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

e scrivere gli indicizzatori :

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

e dichiarando i metodi di estensione :

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

Se non v'è alcun conflitto con una variabile locale o un parametro, si tratta di una questione di stile se utilizzare this o no, così this.MemberOfType e MemberOfType sarebbero equivalenti in quel caso. Vedi anche la parola chiave di base .

Si noti che se un metodo di estensione deve essere chiamato in questa istanza, this è richiesto. Ad esempio se ci si trova all'interno di un metodo non statico di una classe che implementa IEnumerable<> e si desidera chiamare il Count dell'estensione di prima, è necessario utilizzare:

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

e this non può essere omesso lì.

per

Sintassi: for (initializer; condition; iterator)

  • Il ciclo for è comunemente usato quando è noto il numero di iterazioni.
  • Le istruzioni nella sezione di initializer eseguite una sola volta, prima di entrare nel ciclo.
  • La sezione condition contiene un'espressione booleana valutata alla fine di ogni iterazione del ciclo per determinare se il ciclo deve uscire o deve essere eseguito nuovamente.
  • La sezione iterator definisce cosa succede dopo ogni iterazione del corpo del loop.

Questo esempio mostra come for può essere usato per scorrere i caratteri di una stringa:

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

Produzione:

H
e
l
l
o

Live Demo su .NET Fiddle

Tutte le espressioni che definiscono una dichiarazione for sono facoltative; ad esempio, la seguente istruzione è usata per creare un ciclo infinito:

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

La sezione di initializer può contenere più variabili, purché siano dello stesso tipo. La sezione delle condition può essere costituita da qualsiasi espressione che può essere valutata da un bool . E la sezione iterator può eseguire più azioni separate da una virgola:

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

Produzione:

Ciao
hello1
hello12

Live Demo su .NET Fiddle

mentre

L'operatore while esegue iterazioni su un blocco di codice fino a quando la query condizionale è uguale a false o il codice viene interrotto con un'istruzione goto , return , break o throw .

Sintassi per parola chiave while :

while ( condizione ) { blocco di codice; }

Esempio:

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

Produzione:

"Mentre è sul loop numero 1."
"Mentre è sul loop numero 2."
"Mentre è sul loop numero 3."
"Mentre è sul loop numero 4."
"Mentre è sul loop numero 5."

Live Demo su .NET Fiddle

Un ciclo while è Entry Controlled , poiché la condizione viene verificata prima dell'esecuzione del blocco di codice allegato. Ciò significa che il ciclo while non eseguirà le sue istruzioni se la condizione è falsa.

bool a = false;

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

Dare una condizione while senza provvedere a renderlo falso ad un certo punto si tradurrà in un ciclo infinito o infinito. Per quanto possibile, questo dovrebbe essere evitato, tuttavia, ci possono essere alcune circostanze eccezionali quando ne hai bisogno.

È possibile creare un loop come segue:

while (true)
{
//...
}

Si noti che il compilatore C # trasformerà loop come

while (true)
{
// ...
}

o

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

in

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

Si noti che un ciclo while può avere qualsiasi condizione, non importa quanto complessa, purché valuti (o restituisca) un valore booleano (bool). Può anche contenere una funzione che restituisce un valore booleano (in quanto tale una funzione valuta lo stesso tipo di un'espressione come `a == x '). Per esempio,

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

ritorno

MSDN: l'istruzione return termina l'esecuzione del metodo in cui appare e restituisce il controllo al metodo di chiamata. Può anche restituire un valore opzionale. Se il metodo è di tipo vuoto, l'istruzione return può essere omessa.

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
}

nel

L' in parola chiave ha tre usi:

a) Come parte della sintassi in un'istruzione foreach o come parte della sintassi in una query LINQ

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

b) Nel contesto di interfacce generiche e tipi di delegati generici indica la controvarianza per il parametro di tipo in questione:

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

c) Nel contesto della query LINQ fa riferimento alla raccolta che viene interrogata

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

utilizzando

Esistono due tipi di using dell'utilizzo di parole chiave, using statement e using directive :

  1. usando la dichiarazione :

    La parola chiave using assicura che gli oggetti che implementano l'interfaccia IDisposable siano disposti correttamente dopo l'uso. C'è un argomento separato per l' istruzione using

  2. usando la direttiva

    La direttiva using ha tre usi, vedere la pagina msdn per la direttiva using . C'è un argomento separato per la direttiva using .

sigillato

Se applicato a una classe, il modificatore sealed impedisce ad altre classi di ereditarlo.

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

Quando applicato a un metodo virtual (o proprietà virtuale), il modificatore sealed impedisce che questo metodo (proprietà) venga sovrascritto nelle classi derivate.

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

taglia di

Utilizzato per ottenere la dimensione in byte per un tipo non gestito

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

statico

Il modificatore static viene utilizzato per dichiarare un membro statico, che non ha bisogno di essere istanziato per poter accedere, ma è invece accessibile semplicemente attraverso il suo nome, ad es. DateTime.Now .

static può essere utilizzato con classi, campi, metodi, proprietà, operatori, eventi e costruttori.

Mentre un'istanza di una classe contiene una copia separata di tutti i campi di istanza della classe, c'è solo una copia di ogni campo statico.

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 equivale al numero totale di istanze della classe A

Il modificatore statico può anche essere utilizzato per dichiarare un costruttore statico per una classe, per inizializzare dati statici o eseguire codice che deve essere chiamato una sola volta. I costruttori statici vengono chiamati prima che la classe venga referenziata per la prima volta.

class A
{
    static public DateTime InitializationTime;

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

Una static class è contrassegnata con la parola chiave static e può essere utilizzata come contenitore utile per un insieme di metodi che funzionano sui parametri, ma non necessariamente richiedono l'associazione con un'istanza. A causa della natura static della classe, non può essere istanziato, ma può contenere un static constructor . Alcune funzionalità di una static class includono:

  • Non può essere ereditato
  • Non può ereditare da qualcosa di diverso da Object
  • Può contenere un costruttore statico ma non un costruttore di istanze
  • Può contenere solo membri statici
  • È sigillato

Il compilatore è anche amichevole e permetterà allo sviluppatore di sapere se esistono membri di istanze all'interno della classe. Un esempio potrebbe essere una classe statica che converte tra metriche statunitensi e canadesi:

static class ConversionHelper {
    private static double oneGallonPerLitreRate = 0.264172;

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

Quando le classi sono dichiarate statiche:

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

anche tutte le funzioni, proprietà o membri all'interno della classe devono essere dichiarati statici. Nessuna istanza della classe può essere creata. In sostanza, una classe statica consente di creare gruppi di funzioni raggruppate in modo logico.

Dal momento che C # 6 static può essere utilizzato anche a fianco using importare soci e metodi statici. Possono essere usati quindi senza nome della classe.

Vecchio modo, senza using static :

using System;

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

}

Esempio con using static

using static System.Console;

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

}

svantaggi

Mentre le classi statiche possono essere incredibilmente utili, vengono fornite con i loro avvertimenti:

  • Una volta che la classe statica è stata chiamata, la classe viene caricata in memoria e non può essere eseguita attraverso il garbage collector finché non viene scaricato AppDomain che ospita la classe statica.

  • Una classe statica non può implementare un'interfaccia.

int

int è un alias per System.Int32 , che è un tipo di dati per interi a 32 bit con segno. Questo tipo di dati può essere trovato in mscorlib.dll cui fa riferimento implicitamente ogni progetto C # quando vengono creati.

Intervallo: -2.147.483.648 a 2.147.483.647

int int1 = -10007;
var int2 = 2132012521;     

lungo

La parola chiave long viene utilizzata per rappresentare interi a 64 bit con segno. È un alias per il tipo di dati System.Int64 presente in mscorlib.dll , a cui viene fatto implicitamente riferimento ogni progetto C # quando vengono creati.

Qualsiasi variabile lunga può essere dichiarata sia esplicitamente che implicitamente:

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

Una variabile lunga può contenere qualsiasi valore da -9,223,372,036,854,775,808 a 9,223,372,036,854,775,807 e può essere utile in situazioni in cui una variabile deve contenere un valore che supera i limiti di ciò che altre variabili (come la variabile int ) possono contenere.

ulong

Parola chiave utilizzata per numeri interi a 64 bit senza segno. Rappresenta il tipo di dati System.UInt64 trovato in mscorlib.dll cui fa riferimento implicitamente ogni progetto C # quando vengono creati.

Intervallo: da 0 a 18.446.744.073.709.551.615

ulong veryLargeInt = 18446744073609451315;
var anotherVeryLargeInt = 15446744063609451315UL;

dinamico

La parola chiave dynamic viene utilizzata con oggetti digitati dinamicamente . Gli oggetti dichiarati come dynamic escludono i controlli statici in fase di compilazione e vengono invece valutati in fase di runtime.

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

L'esempio seguente utilizza la dynamic con la libreria di Newtonsoft Json.NET, per leggere facilmente i dati da un file JSON deserializzato.

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
}

Esistono alcune limitazioni associate alla parola chiave dinamica. Uno di questi è l'uso di metodi di estensione. Nell'esempio seguente viene aggiunto un metodo di estensione per la stringa: SayHello .

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

Il primo approccio sarà chiamarlo come al solito (come per una stringa):

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

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

Nessun errore di compilazione, ma a runtime si ottiene una RuntimeBinderException . La soluzione per questo sarà chiamare il metodo di estensione tramite la classe statica:

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

virtuale, override, nuovo

virtuale e override

La parola chiave virtual consente a un metodo, proprietà, indicizzatore o evento di essere sovrascritto da classi derivate e presenta un comportamento polimorfico. (I membri non sono virtuali di default in C #)

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

Per sovrascrivere un membro, la parola chiave override viene utilizzata nelle classi derivate. (Nota la firma dei membri deve essere identica)

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

Il comportamento polimorfico dei membri virtuali significa che quando viene invocato, il membro effettivo che viene eseguito viene determinato in fase di esecuzione anziché in fase di compilazione. Il membro che sovrascrive nella classe più derivata l'oggetto particolare è un'istanza di sarà quella eseguita.

In breve, l'oggetto può essere dichiarato di tipo BaseClass in fase di compilazione ma se in fase di esecuzione è un'istanza di DerivedClass il membro sottoposto a override verrà eseguito:

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

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

L'override di un metodo è facoltativo:

public class SecondDerivedClass: DerivedClass {}

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

nuovo

Poiché solo i membri definiti come virtual sono sovrascrivibili e polimorfici, una classe derivata che ridefinisce un membro non virtuale potrebbe portare a risultati imprevisti.

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!    

Quando ciò accade, il membro eseguito viene sempre determinato al momento della compilazione in base al tipo dell'oggetto.

  • Se l'oggetto è dichiarato di tipo BaseClass (anche se in fase di esecuzione è di una classe derivata) viene eseguito il metodo di BaseClass
  • Se l'oggetto viene dichiarato di tipo DerivedClass quindi il metodo di DerivedClass viene eseguito.

Questo di solito è un incidente (quando un membro viene aggiunto al tipo base dopo che uno identico è stato aggiunto al tipo derivato) e in questi scenari viene generato un avviso del compilatore CS0108 .

Se è stato intenzionale, la new parola chiave viene utilizzata per sopprimere l'avviso del compilatore (e informare gli altri sviluppatori delle tue intenzioni!). il comportamento rimane lo stesso, la new parola chiave sopprime solo l'avviso del compilatore.

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! 

L'uso dell'override non è facoltativo

A differenza di C ++, l'uso della parola chiave override non è facoltativo:

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

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

L'esempio precedente causa inoltre un avviso CS0108 , poiché B.Foo() non sovrascrive automaticamente A.Foo() . Aggiungi override quando l'intenzione è di ignorare la classe base e causare comportamenti polimorfici, aggiungere new quando si desidera un comportamento non polimorfico e risolvere la chiamata utilizzando il tipo statico. Quest'ultimo dovrebbe essere usato con cautela, in quanto potrebbe causare grave confusione.

Il seguente codice produce persino un errore:

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

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

Le classi derivate possono introdurre il polimorfismo

Il seguente codice è perfettamente valido (anche se raro):

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

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

Ora tutti gli oggetti con un riferimento statico di B (e le sue derivate) usano il polimorfismo per risolvere Foo() , mentre i riferimenti di A usano 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";

I metodi virtuali non possono essere privati

Il compilatore C # è severo nel prevenire costrutti senza senso. I metodi contrassegnati come virtual non possono essere privati. Poiché un metodo privato non può essere visto da un tipo derivato, non può essere sovrascritto. Questo non riesce a compilare:

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

asincrono, attendi

La parola chiave await stata aggiunta come parte della release C # 5.0 supportata da Visual Studio 2012 in poi. Sfrutta la Task Parallel Library (TPL) che ha reso la multi-threading relativamente più semplice. Le parole chiave async e await sono utilizzate in coppia con la stessa funzione mostrata di seguito. La parola chiave await viene utilizzata per sospendere l'esecuzione del metodo asincrono corrente fino a quando l'attività asincrona attesa viene completata e / oi relativi risultati restituiti. Per utilizzare la parola chiave await , il metodo che lo utilizza deve essere contrassegnato con la parola chiave async .

L'uso async con void è fortemente scoraggiato. Per maggiori informazioni puoi guardare qui .

Esempio:

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

Produzione:

"Avvio di un processo inutile ..."

** ... 1 secondo di ritardo ... **

"Un processo inutile ha richiesto 1000 millisecondi."

La parola chiave pairs async e await può essere omessa se un Task o Task<T> return method restituisce solo una singola operazione asincrona.

Piuttosto che questo:

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

Si preferisce fare questo:

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

In C # 5.0 await non può essere utilizzato in catch e finally .

6.0

Con C # 6.0 è possibile await nel catch e finally .

carbonizzare

Un carattere è una singola lettera memorizzata all'interno di una variabile. È un tipo di valore incorporato che richiede due byte di spazio di memoria. Rappresenta il tipo di dati System.Char trovato in mscorlib.dll cui fa riferimento implicitamente ogni progetto C # quando vengono creati.

Ci sono molti modi per farlo.

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

Un char può essere implicitamente convertito in ushort, int, uint, long, ulong, float, double, o decimal e restituirà il valore intero di quel char.

ushort u = c;

restituisce 99 ecc.

Tuttavia, non ci sono conversioni implicite da altri tipi a char. Invece devi castarli.

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

serratura

lock fornisce thread-safety per un blocco di codice, in modo che sia accessibile da un solo thread all'interno dello stesso processo. Esempio:

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

Casi d'uso:

Ogni volta che si dispone di un blocco di codice che potrebbe produrre effetti collaterali se eseguito da più thread contemporaneamente. La parola chiave di blocco insieme a un oggetto di sincronizzazione condiviso ( _objLock nell'esempio) può essere utilizzata per impedirlo.

Si noti che _objLock non può essere null e più thread che eseguono il codice devono utilizzare la stessa istanza di oggetto (rendendola un campo static o utilizzando la stessa istanza di classe per entrambi i thread)

Dal lato del compilatore, la parola chiave lock è uno zucchero sintattico che viene sostituito da Monitor.Enter(_lockObj); e Monitor.Exit(_lockObj); . Quindi se sostituisci il blocco circondando il blocco di codice con questi due metodi, otterresti gli stessi risultati. È possibile visualizzare il codice effettivo in zucchero sintattico in C # - esempio di blocco

nullo

Una variabile di un tipo di riferimento può contenere un riferimento valido a un'istanza o un riferimento null. Il riferimento null è il valore predefinito delle variabili di tipo di riferimento, nonché i tipi di valori nullable.

null è la parola chiave che rappresenta un riferimento null.

Come espressione, può essere usato per assegnare il riferimento null alle variabili dei tipi sopra citati:

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

Ai tipi di valori non annullabili non può essere assegnato un riferimento null. Tutti i seguenti incarichi non sono validi:

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

Il riferimento null non deve essere confuso con istanze valide di vari tipi come:

  • una lista vuota ( new List<int>() )
  • una stringa vuota ( "" )
  • il numero zero ( 0 , 0f , 0m )
  • il carattere null ( '\0' )

A volte, è significativo verificare se qualcosa è nullo o un oggetto vuoto / predefinito. Il metodo System.String.IsNullOrEmpty (String) può essere utilizzato per verificare questo, oppure è possibile implementare il proprio metodo equivalente.

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

interno

La parola chiave internal è un modificatore di accesso per i membri di tipo e tipo. Tipi interni o membri sono accessibili solo all'interno di file nello stesso assembly

utilizzo:

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

La differenza tra i diversi modificatori di accesso è chiarita qui

Modificatori di accesso

pubblico

È possibile accedere al tipo o al membro tramite qualsiasi altro codice nello stesso assembly o in un altro assembly che lo faccia riferimento.

privato

È possibile accedere al tipo o al membro solo tramite codice nella stessa classe o struttura.

protetta

È possibile accedere al tipo o al membro solo tramite codice nella stessa classe o struttura o in una classe derivata.

interno

È possibile accedere al tipo o al membro tramite qualsiasi codice nello stesso assembly, ma non da un altro assembly.

protetto interno

È possibile accedere al tipo o al membro da qualsiasi codice nello stesso assembly o da qualsiasi classe derivata in un altro assembly.

Se non è impostato alcun modificatore di accesso, viene utilizzato un modificatore di accesso predefinito. Quindi c'è sempre qualche forma di modificatore di accesso anche se non è impostato.

dove

where può servire a due scopi in C #: digita il vincolo in un argomento generico e filtra le query LINQ.

In una classe generica, consideriamo

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

T è chiamato parametro di tipo. La definizione della classe può imporre vincoli sui tipi reali che possono essere forniti per T.

I seguenti tipi di vincoli possono essere applicati:

  • tipo di valore
  • tipo di riferimento
  • costruttore predefinito
  • eredità e attuazione

tipo di valore

In questo caso possono essere fornite solo struct s (questo include i tipi di dati 'primitivi' come int , boolean ecc.)

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

tipo di riferimento

In questo caso possono essere forniti solo tipi di classe

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

valore ibrido / tipo di riferimento

Occasionalmente si desidera limitare gli argomenti di tipo a quelli disponibili in un database e questi di solito si associano a tipi di valore e stringhe. Poiché tutte le restrizioni di tipo devono essere soddisfatte, non è possibile specificare where T : struct or string (questa non è una sintassi valida). Una soluzione alternativa consiste nel limitare gli argomenti di tipo a IConvertible che include tipi di "... Booleano, SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double, Decimal, DateTime, Char e String. " È possibile che altri oggetti implementino IConvertible, sebbene ciò sia raro nella pratica.

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

costruttore predefinito

Saranno consentiti solo i tipi che contengono un costruttore predefinito. Ciò include i tipi di valore e le classi che contengono un costruttore predefinito (senza parametri)

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

eredità e attuazione

Possono essere forniti solo i tipi che ereditano da una determinata classe base o implementano una determinata interfaccia.

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


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

Il vincolo può anche fare riferimento a un altro parametro di tipo:

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

È possibile specificare più vincoli per un argomento di tipo:

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

Gli esempi precedenti mostrano vincoli generici su una definizione di classe, ma i vincoli possono essere utilizzati ovunque sia fornito un argomento di tipo: classi, strutture, interfacce, metodi, ecc.

where può anche essere una clausola LINQ. In questo caso è analogo a 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

La parola chiave extern viene utilizzata per dichiarare metodi implementati esternamente. Può essere utilizzato insieme all'attributo DllImport per chiamare nel codice non gestito utilizzando i servizi di interoperabilità. che in questo caso arriverà con static modificatore static

Per esempio:

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

Ciò utilizza il metodo SetForegroundWindow importato dalla libreria User32.dll

Questo può anche essere usato per definire un alias di assembly esterno. che ci permette di fare riferimento a diverse versioni degli stessi componenti dal singolo assemblaggio.

Per fare riferimento a due assembly con gli stessi nomi di tipo completi, è necessario specificare un alias al prompt dei comandi, come indicato di seguito:

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

Questo crea gli alias esterni GridV1 e GridV2. Per utilizzare questi alias all'interno di un programma, consultali usando la parola chiave extern. Per esempio:

extern alias GridV1;
extern alias GridV2;

bool

Parola chiave per la memorizzazione dei valori booleani true e false . bool è un alias di System.Boolean.

Il valore predefinito di un bool è falso.

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

Per un bool per consentire valori nulli deve essere inizializzato come bool ?.

Il valore predefinito di un bool? è zero.

bool? a // default value is null

quando

Il when una parola chiave viene aggiunta in C # 6 e viene utilizzata per il filtraggio delle eccezioni.

Prima dell'introduzione della parola chiave when , si poteva avere una clausola catch per ogni tipo di eccezione; con l'aggiunta della parola chiave, ora è possibile un controllo più dettagliato.

A when expression è associata a un ramo catch e solo se la condizione when è true , la clausola catch verrà eseguita. È possibile avere diverse clausole di catch con gli stessi tipi di classi di eccezioni e diverse when condizioni.

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

non verificato

La parola chiave unchecked impedisce al compilatore di verificare la presenza di overflow / underflow.

Per esempio:

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

Senza la parola chiave non unchecked , nessuna delle due operazioni di aggiunta verrà compilata.

Quando è utile?

Questo è utile in quanto può aiutare ad accelerare i calcoli che sicuramente non avranno overflow dal momento che il controllo dell'overflow richiede tempo, o quando un overflow / underflow è un comportamento desiderato (ad esempio, quando si genera un codice hash).

vuoto

La parola riservata "void" è un alias di tipo System.Void e ha due usi:

  1. Dichiara un metodo che non ha un valore di ritorno:
public void DoSomething()
{
    // Do some work, don't return any value to the caller.
}

Un metodo con un tipo restituito di vuoto può ancora avere la parola chiave return nel suo corpo. Ciò è utile quando si desidera uscire dall'esecuzione del metodo e restituire il flusso al chiamante:

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

    if (condition)
        return;

    // Do some more work if the condition evaluated to false.
}
  1. Dichiarare un puntatore a un tipo sconosciuto in un contesto non sicuro.

In un contesto non sicuro, un tipo può essere un tipo di puntatore, un tipo di valore o un tipo di riferimento. Una dichiarazione del tipo puntatore è solitamente type* identifier , dove il tipo è un tipo noto, ad esempio int* myInt , ma può anche essere void* identifier , in cui il tipo è sconosciuto.

Si noti che la dichiarazione di un tipo di puntatore void è sconsigliata da Microsoft.

se, se ... altro, se ... altro se


L'istruzione if viene utilizzata per controllare il flusso del programma. Un'istruzione if identifica quale istruzione eseguire in base al valore di un'espressione Boolean .

Per una singola istruzione, le braces {} sono facoltative ma consigliate.

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

L' if può anche avere una clausola else , che verrà eseguita nel caso in cui la condizione sia falsa:

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"

Il if ... else if construct ti permette di specificare più condizioni:

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"

Importante notare che se una condizione è soddisfatta nell'esempio precedente, il controllo salta altri test e salta alla fine di quel particolare if else construct.So, l' ordine dei test è importante se si sta usando if .. else if construct

Le espressioni booleane C # utilizzano la valutazione di cortocircuito . Questo è importante nei casi in cui le condizioni di valutazione possono avere effetti collaterali:

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

Non vi è alcuna garanzia che someOtherBooleanMethodWithSideEffects verrà effettivamente eseguito.

È anche importante nei casi in cui condizioni precedenti assicurano che è "sicuro" valutare quelli successivi. Per esempio:

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

L'ordine è molto importante in questo caso perché, se invertiamo l'ordine:

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

getterà una NullReferenceException se someCollection è null .

fare

L'operatore do esegue iterazioni su un blocco di codice fino a quando una query condizionale è uguale a false. Il ciclo do-while può anche essere interrotto da un'istruzione goto , return , break o throw .

La sintassi per la parola chiave do è:

do { blocco di codice; } while ( condizione );

Esempio:

int i = 0;

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

Produzione:

"Do is on loop numero 1."
"Do is on loop numero 2."
"Do is on loop numero 3."
"Do is on loop numero 4."
"Do is on loop numero 5."

A differenza del ciclo while , il ciclo do-while è Exit Controlled . Ciò significa che il ciclo do-while eseguirà le sue istruzioni almeno una volta, anche se la condizione non riesce la prima volta.

bool a = false;

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

operatore

La maggior parte degli operatori integrati (inclusi gli operatori di conversione) può essere sovraccaricata utilizzando la parola chiave operator insieme ai modificatori public e static .

Gli operatori sono disponibili in tre forme: operatori unari, operatori binari e operatori di conversione.

Gli operatori unari e binari richiedono almeno un parametro dello stesso tipo del tipo contenente e alcuni richiedono un operatore di corrispondenza complementare.

Gli operatori di conversione devono convertire in o dal tipo di allegato.

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

}

Esempio

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

Un tipo di struct è un tipo di valore che viene in genere utilizzato per incapsulare piccoli gruppi di variabili correlate, come le coordinate di un rettangolo o le caratteristiche di un articolo in un inventario.

Le classi sono tipi di riferimento, le strutture sono tipi di valore.

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

Le strutture possono anche contenere costruttori, costanti, campi, metodi, proprietà, indicizzatori, operatori, eventi e tipi annidati, sebbene se siano richiesti più di questi membri, dovresti prendere in considerazione la creazione di una classe invece.


Alcuni suggerimenti da MS su quando usare struct e quando usare la classe:

TENERE CONTO

definire una struct invece di una classe se le istanze del tipo sono piccole e comunemente di breve durata o sono comunemente incorporate in altri oggetti.

EVITARE

definire una struttura a meno che il tipo abbia tutte le seguenti caratteristiche:

  • Rappresenta logicamente un singolo valore, simile ai tipi primitivi (int, double, ecc.)
  • Ha una dimensione dell'istanza inferiore a 16 byte.
  • È immutabile.
  • Non dovrà essere incassato frequentemente.

interruttore

L'istruzione switch è un'istruzione di controllo che seleziona una sezione switch da eseguire da un elenco di candidati. Una dichiarazione switch include una o più sezioni switch. Ogni sezione switch contiene una o più etichette case seguite da una o più istruzioni. Se nessuna etichetta case contiene un valore corrispondente, il controllo viene trasferito alla sezione default , se ce n'è uno. Caso fall-through non è supportato in C #, in senso stretto. Tuttavia, se 1 o più etichette del case sono vuote, l'esecuzione seguirà il codice del successivo blocco del case che contiene il codice. Ciò consente il raggruppamento di più case con la stessa implementazione. Nell'esempio seguente, se il month uguale a 12, il codice nel case 2 verrà eseguito poiché le etichette del case 12 1 e 2 sono raggruppate. Se un blocco del case non è vuoto, deve essere presente break prima della successiva etichetta del case , altrimenti il ​​compilatore segnala un errore.

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

Un case può essere etichettato solo da un valore noto al momento della compilazione (es. 1 , "str" , Enum.A ), quindi una variable non è un'etichetta case valida, ma un valore const o un valore Enum è (così come qualsiasi valore letterale).

interfaccia

Un interface contiene le firme di metodi, proprietà ed eventi. Le classi derivate definiscono i membri poiché l'interfaccia contiene solo la dichiarazione dei membri.

Un'interfaccia è dichiarata usando la parola chiave interface .

interface IProduct
{
    decimal Price { get; }
}

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

pericoloso

La parola chiave unsafe può essere utilizzata nelle dichiarazioni del tipo o del metodo o per dichiarare un blocco in linea.

Lo scopo di questa parola chiave è di abilitare l'uso del sottoinsieme non sicuro di C # per il blocco in questione. Il sottoinsieme non sicuro include funzionalità come puntatori, allocazione dello stack, array di tipo C e così via.

Il codice non sicuro non è verificabile ed è per questo che il suo utilizzo è scoraggiato. La compilazione di codice non sicuro richiede il passaggio di un passaggio al compilatore C #. Inoltre, il CLR richiede che l'assembly in esecuzione abbia piena fiducia.

Nonostante queste limitazioni, il codice non sicuro ha degli usi validi per rendere alcune operazioni più performanti (ad esempio l'indicizzazione degli array) o più semplici (ad es. Interop con alcune librerie non gestite).

Come un esempio molto semplice

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

Mentre lavoriamo con i puntatori, possiamo modificare direttamente i valori delle locazioni di memoria, piuttosto che doverli affrontare per nome. Si noti che questo spesso richiede l'uso della parola chiave fissa per prevenire possibili corruzioni della memoria in quanto il garbage collector sposta le cose intorno (altrimenti, si potrebbe ottenere l' errore CS0212 ). Poiché non è possibile scrivere su una variabile che è stata "riparata", spesso è necessario avere un secondo puntatore che inizi a puntare alla stessa posizione del primo.

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

Produzione:

1
4
9
16
25
36
49
64
81
100

unsafe consente inoltre l'uso di stackalloc che allocherà memoria nello stack come _alloca nella libreria di runtime C. Possiamo modificare l'esempio precedente per usare stackalloc come segue:

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

(L'uscita è la stessa di sopra)

implicito

La parola chiave implicit viene utilizzata per sovraccaricare un operatore di conversione. Ad esempio, puoi dichiarare una classe Fraction che dovrebbe essere automaticamente convertita in double quando necessario e che può essere convertita automaticamente da int :

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

vero falso

Le parole chiave true e false hanno due usi:

  1. Come valori booleani letterali
var myTrueBool = true;
var myFalseBool = false;
  1. Come operatori che possono essere sovraccaricati
public static bool operator true(MyClass x)
{
    return x.value >= 0;
}

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

Il sovraccarico dell'operatore falso era utile prima del C # 2.0, prima dell'introduzione dei tipi Nullable .
Un tipo che sovraccarica l'operatore true , deve anche sovraccaricare l'operatore false .

stringa

string è un alias del tipo di dati .NET System.String , che consente di memorizzare testo (sequenze di caratteri).

Notazione:

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

Ogni carattere nella stringa è codificato in UTF-16, il che significa che ogni carattere richiederà almeno 2 byte di spazio di archiviazione.

USHORT

Un tipo numerico utilizzato per memorizzare interi positivi a 16 bit. ushort è un alias per System.UInt16 e occupa 2 byte di memoria.

L'intervallo valido è compreso tra 0 e 65535 .

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

sbyte

Un tipo numerico utilizzato per memorizzare interi con segno a 8 bit. sbyte è un alias per System.SByte e occupa 1 byte di memoria. Per l'equivalente senza segno, usa il byte .

L'intervallo valido è compreso tra -127 e 127 (l'avanzo viene utilizzato per memorizzare il segno).

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

var

Una variabile locale implicitamente tipizzata che è fortemente tipizzata come se l'utente avesse dichiarato il tipo. A differenza di altre dichiarazioni variabili, il compilatore determina il tipo di variabile che rappresenta in base al valore che gli viene assegnato.

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

A differenza di altri tipi di variabili, le definizioni di variabili con questa parola chiave devono essere inizializzate quando dichiarate. Ciò è dovuto alla parola chiave var che rappresenta una variabile tipizzata implicitamente.

var i;
i = 10;

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

La parola chiave var può anche essere utilizzata per creare nuovi tipi di dati al volo. Questi nuovi tipi di dati sono noti come tipi anonimi . Sono abbastanza utili, in quanto consentono a un utente di definire un insieme di proprietà senza dover dichiarare esplicitamente qualsiasi tipo di tipo di oggetto per primo.

Semplice tipo anonimo

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

Query LINQ che restituisce un tipo anonimo

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

È possibile utilizzare la parola chiave var nell'istruzione foreach

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

    return false;
}

delegare

I delegati sono tipi che rappresentano un riferimento a un metodo. Sono usati per passare metodi come argomenti ad altri metodi.

I delegati possono contenere metodi statici, metodi di istanza, metodi anonimi o espressioni lambda.

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

    public delegate bool DelegateInvoker( string input );

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

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

Quando si assegna un metodo a un delegato è importante notare che il metodo deve avere lo stesso tipo di ritorno e anche i parametri. Ciò differisce dal sovraccarico del metodo "normale", in cui solo i parametri definiscono la firma del metodo.

Gli eventi sono costruiti in cima ai delegati.

evento

Un event consente allo sviluppatore di implementare un modello di notifica.

Semplice esempio

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

Riferimento MSDN

parziale

La parola chiave partial può essere utilizzata durante la definizione del tipo di classe, struct o interfaccia per consentire la divisione della definizione del tipo in più file. Questo è utile per incorporare nuove funzionalità nel codice generato automaticamente.

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

Nota: una classe può essere suddivisa in un numero qualsiasi di file. Tuttavia, tutte le dichiarazioni devono trovarsi nello stesso spazio dei nomi e nello stesso assembly.

I metodi possono anche essere dichiarati parziali usando la parola chiave partial . In questo caso un file conterrà solo la definizione del metodo e un altro file conterrà l'implementazione.

Un metodo parziale ha la propria firma definita in una parte di un tipo parziale e la sua implementazione definita in un'altra parte del tipo. I metodi parziali consentono ai progettisti di classi di fornire hook di metodo, simili ai gestori di eventi, che gli sviluppatori possono decidere di implementare o meno. Se lo sviluppatore non fornisce un'implementazione, il compilatore rimuove la firma al momento della compilazione. Le seguenti condizioni si applicano ai metodi parziali:

  • Le firme in entrambe le parti del tipo parziale devono corrispondere.
  • Il metodo deve restituire void.
  • Non sono ammessi modificatori di accesso. I metodi parziali sono implicitamente privati.

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

Nota: anche il tipo contenente il metodo parziale deve essere dichiarato parziale.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow