Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
La classe ThrowHelper è un tipo helper che può essere usato per generare in modo efficiente le eccezioni. È progettato per supportare le API guard e deve essere usato principalmente in situazioni in cui gli sviluppatori necessitano di un controllo granulare sui tipi di eccezione generati o sull'esatto messaggio di eccezione da includere.
API della piattaforma:
ThrowHelper,Guard
Sintassi
La ThrowHelper classe espone una serie di ThrowException API che offrono un mapping 1:1 ai costruttori di tutti i tipi di eccezione disponibili. Questi metodi devono essere usati al posto dell'istruzione classica throw new Exception(...) nel codice per creare codice più piccolo ed efficiente. Basta:
// Replace this...
throw new InvalidOperationException("Some custom message from my library");
// ...with this
ThrowHelper.ThrowInvalidOperationException("Some custom message from my library");
Annotazioni
Questa modifica comporta anche un'analisi dello stack leggermente diversa, perché l'eccezione viene generata da questo metodo helper anziché direttamente dal chiamante originale. Il risultato pratico è comunque identico a quello di generare direttamente un'eccezione.
Gli Guard API forniscono un'astrazione facile da usare su molti controlli comuni che potrebbero essere eseguiti, quindi si consiglia di verificare prima di tutto se è disponibile una Guard API che può essere usata e ripiegare su ThrowHelper solo se non è disponibile. Come accennato in precedenza, questo potrebbe essere se la libreria deve generare un tipo di eccezione specifico non presente nelle Guard API o se è necessario il controllo completo sugli argomenti esatti inclusi nell'eccezione generata.
Esistono anche overload generici per tutte le API disponibili, che possono essere usati all'interno di espressioni che richiedono un tipo di ritorno specifico, ad esempio un'espressione switch. Questi overload non restituiscono mai alcun valore, ma il tipo restituito viene dichiarato per consentire al compilatore C# di accettare l'utilizzo di queste API all'interno di espressioni che non funzionano con un metodo che restituisce void. Si consideri l'esempio seguente:
int result = text switch
{
"cat" => 0,
"dog" => 1,
_ => ThrowHelper.ThrowArgumentException<int>(nameof(text))
};
In questo caso, ThrowHelper viene usato all'interno di un'espressione che richiede un tipo restituito di int, in modo da poter usare l'overload generico di ThrowArgumentException per consentirlo. Il sovraccarico generico funziona anche con modelli come gli operatori ternari (x ? a : b), gli operatori null-coalescenti (x = a ?? b) e gli operatori di assegnazione null-coalescenti (x ??= y).
Methods
La classe ThrowHelper contiene numerosi metodi e overload diversi. Forniscono l'accesso alle seguenti eccezioni (e mappano tutti i loro costruttori pubblici):
- ArrayTypeMismatchException
- Argumentexception
- ArgumentNullException
- ArgumentOutOfRangeException
- COMException
- ExternalException
- Formatexception
- Insufficientmemoryexception
- InvalidDataException
- InvalidOperationException
- LockRecursionException
- MissingFieldException
- MissingMemberException
- MissingMethodException
- NotSupportedException
- Objectdisposedexception
- OperationCanceledException
- PlatformNotSupportedException
- Synchronizationlockexception
- TimeoutException
- UnauthorizedAccessException
- Win32Exception
È possibile visualizzare i costruttori disponibili per ognuno di questi tipi di eccezione dalla documentazione collegata. Le rispettive ThrowHelper API offrono una serie di overload corrispondenti ai costruttori esistenti per quel tipo specificato. Ogni API è semplicemente denominata "Throw" + il tipo di eccezione.
Dettagli tecnici
La sezione seguente descrive l'impatto del passaggio alle API ThrowHelper rispetto alle istruzioni standard throw new Exception in relazione alla generazione del codice. Come accennato in precedenza, conoscere questi dettagli non è necessario usare queste API, ma per gli sviluppatori che hanno familiarità con il funzionamento del runtime o con il funzionamento del codice JIT durante l'esecuzione, questa sezione fornisce una spiegazione più chiara del motivo per cui l'uso del ThrowHelper modello comporta codice più compatto e veloce.
Prima di tutto, considerare il codice che il compilatore JIT produce effettivamente in queste situazioni. Questo esempio usa il codice generato in .NET Core 3.1 x64 come riferimento, ma lo stesso concetto si applica anche ad altri runtime e architetture.
Si consideri questo tipo semplice:
public struct ExceptionTest
{
private int number;
public int GetValueIfNotZeroOrThrow()
{
if (number == 0)
{
throw new InvalidOperationException("The value must be != 0");
}
return number;
}
}
Questo tipo è destinato solo a mostrare l'uso delle ThrowHelper API. Controlla il valore del number campo e genera se è 0. Esaminare il codice assembly generato:
ExceptionTest.GetValueIfNotZeroOrThrow()
mov eax, [rcx] ; read number
test eax, eax ; if (number != 0)
je short L0011 ; jump to faulting path (if == 0)
ret ; return (number is already in eax)
mov rcx, 0x7ff95cbaca80 ; exception setup...
call 0x00007ff9bc6178f0
mov rsi, rax
mov ecx, 1
mov rdx, 0x7ff96590c080
call 0x00007ff9bc7403e0
mov rdx, rax
mov rcx, rsi
call System.InvalidOperationException..ctor(System.String)
mov rcx, rsi
call 0x00007ff9bc5db3a0
int3 ; finally throw the exception
Il codice contiene molte righe dedicate alla creazione dell'eccezione generata. Ciò può causare un aumento elevato delle dimensioni del codice ogni volta che si dispone di molti di questi controlli, perché riduce l'efficienza della cache delle istruzioni. In generale, si desidera che le dimensioni dei metodi siano il più piccole possibile.
Il metodo precedente, riscritto usando le ThrowHelper API, è simile al seguente:
public int GetValueIfNotZeroOrThrow()
{
if (number == 0)
{
ThrowHelper.ThrowInvalidOperationException("The value must be != 0");
}
return number;
}
Che porta al seguente assembly:
ExceptionTest.GetValueIfNotZeroOrThrow2()
mov eax, [rcx] ; load number
test eax, eax ; if (number != 0)
je short L000f ; faulting path (if == 0)
ret ; return number (already in eax)
mov rcx, 0x23435d85b98 ; load the exception message
mov rcx, [rcx]
call ThrowHelper.ThrowInvalidOperationException(System.String) ; ThrowHelper call!
int3 ; return with fault
È possibile notare che l'assembly risultante è molto più piccolo di prima. Il codice per creare l'eccezione viene spostato all'esterno del metodo, ciò significa che è possibile riutilizzare lo stesso codice dalle ThrowHelper API, con argomenti diversi, anziché inserire l'eccezione in ciascun metodo. In questo caso, sono disponibili solo due mov istruzioni per caricare il tipo di eccezione string e quindi passa al metodo ThrowHelper.ThrowInvalidOperationException. Il compilatore JIT è in grado di vedere che quel metodo genera sempre un'eccezione, quindi non inserisce mai in linea quella chiamata. E assicura di riscrivere i rami condizionali nel modo migliore possibile (in particolare, sapendo quale ramo deve essere eseguito, senza errori).
Examples
Altri esempi sono disponibili negli unit test.
.NET Community Toolkit