Indicare al debugger cosa mostrare usando l'attributo DebuggerDisplay (C#, Visual Basic, F#, C++/CLI)
Il DebuggerDisplayAttribute controlla la modalità di visualizzazione di un oggetto, una proprietà o un campo nelle finestre delle variabili del debugger. Questo attributo può essere applicato ai tipi (classi, struct, enumerazioni, delegati), ma in genere viene applicato solo a classi e struct. Se applicato a un tipo di base, l'attributo si applica anche a una sottoclasse.
L'attributo DebuggerDisplay
ha un singolo argomento, ovvero una stringa da visualizzare nella colonna valore per le istanze del tipo. Questa stringa può contenere parentesi graffe ({
e }
). Il testo all'interno di una coppia di parentesi graffe viene valutato come campo, proprietà o metodo.
Se una classe ha un metodo di ToString()
sottoposto a override, il debugger usa il metodo sottoposto a override anziché il {<typeName>}
predefinito . Pertanto, se è stato eseguito l'override del metodo ToString()
, il debugger usa il metodo sottoposto a override anziché il {<typeName>}
predefinito e non è necessario usare DebuggerDisplay
. Se si usano entrambi, l'attributo DebuggerDisplay
ha la precedenza sul metodo di ToString()
sottoposto a override. L'attributo DebuggerDisplay
ha anche la precedenza sul metodo ToString()
sottoposto a override in una sottoclasse.
Se il debugger valuta questa chiamata implicita ToString()
dipende da un'impostazione utente nella finestra di dialogo Tools/Options/Debugging.
Importante
Se la casella di controllo Mostra struttura non elaborata di oggetti nelle finestre delle variabili è selezionata nella finestra di dialogo Strumenti /Opzioni/Debug, l'attributo DebuggerDisplay
viene ignorato.
Nota
Per il codice nativo, questo attributo è supportato solo nel codice C++/CLI.
Nella tabella seguente vengono illustrati alcuni possibili usi dell'attributo DebuggerDisplay
e degli output di esempio.
Attributo | Output visualizzato nella colonna Valore |
---|---|
[DebuggerDisplay("x = {x} y = {y}")] Utilizzato in un tipo con campi x e y . |
x = 5 y = 18 |
[DebuggerDisplay("String value is {getString()}")] sintassi dei parametri può variare tra i linguaggi. Pertanto, usarlo con cura. |
String value is [5, 6, 6] |
DebuggerDisplay
può anche accettare parametri denominati.
Parametri | Scopo |
---|---|
Name , Type |
Questi parametri influiscono sulle colonne Nome e Tipo delle finestre delle variabili. Possono essere impostati su stringhe usando la stessa sintassi del costruttore. L'utilizzo eccessivo di questi parametri, o l'uso non corretto, può causare un output confuso. |
Target , TargetTypeName |
Specifica il tipo di destinazione quando l'attributo viene usato a livello di assembly. |
Il file autoexp.cs usa l'attributo DebuggerDisplay a livello di assembly. Il file autoexp.cs determina le espansioni predefinite usate da Visual Studio per gli oggetti .NET. È possibile esaminare il file di autoexp.cs per esempi di come usare l'attributo DebuggerDisplay oppure modificare e compilare il file autoexp.cs per modificare le espansioni predefinite. Assicurarsi di eseguire il backup del file autoexp.cs prima di modificarlo.
Per compilare autoexp.cs, aprire un prompt dei comandi per gli sviluppatori per VS2015 ed eseguire i comandi seguenti
cd <directory containing autoexp.cs>
csc /t:library autoexp.cs
Le modifiche apportate a autoexp.dll verranno prelevate nella sessione di debug successiva.
Uso di espressioni in DebuggerDisplay
Sebbene sia possibile usare un'espressione generale tra parentesi graffe in un attributo DebuggerDisplay, questa procedura non è consigliata.
Un'espressione generale in DebuggerDisplay ha accesso implicito al puntatore this
solo per l'istanza attuale del tipo di destinazione. L'espressione non ha accesso ad alias, variabili locali o puntatori. Se l'espressione fa riferimento alle proprietà, gli attributi di tali proprietà non vengono elaborati. Ad esempio, il codice C# [DebuggerDisplay("Object {count - 2}")]
verrebbe visualizzato come Object 6
se il campo count
fosse 8.
L'uso delle espressioni in DebuggerDisplay può causare i problemi seguenti:
La valutazione delle espressioni è l'operazione più costosa nel debugger e l'espressione viene valutata ogni volta che viene visualizzata. Ciò può causare problemi di prestazioni durante l'esecuzione del codice. Ad esempio, un'espressione complessa usata per visualizzare i valori in una raccolta o un elenco può essere molto lenta quando il numero di elementi è elevato.
Le espressioni vengono valutate dall'analizzatore di espressioni del linguaggio del frame dello stack corrente e non dall'analizzatore del linguaggio in cui è stata scritta l'espressione. Ciò può causare risultati imprevedibili quando le lingue sono diverse.
La valutazione di un'espressione può modificare lo stato dell'applicazione. Ad esempio, un'espressione che imposta il valore di una proprietà modifica il valore della proprietà nel codice in esecuzione.
Un modo per ridurre i possibili problemi di valutazione delle espressioni consiste nel creare una proprietà privata che esegue l'operazione e restituisce una stringa. L'attributo DebuggerDisplay può quindi visualizzare il valore di tale proprietà privata. L'esempio seguente implementa questo modello:
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public sealed class MyClass
{
public int count { get; set; }
public bool flag { get; set; }
private string DebuggerDisplay
{
get
{
return string.Format("Object {0}", count - 2);
}
}
}
Il suffisso "nq" indica all'analizzatore di espressioni di rimuovere le virgolette quando viene visualizzato il valore finale (nq = senza virgolette).
Esempio
Nell'esempio di codice seguente viene illustrato come usare DebuggerDisplay
, insieme a DebuggerBrowsable
e DebuggerTypeProxy
. Quando viene visualizzato in una finestra delle variabili del debugger, ad esempio la finestra di controllo del Watch, genera un'espansione simile alla seguente:
Nome | Valore | tipo |
---|---|---|
Chiave | "tre" | oggetto {string} |
Valore | 3 | oggetto {int} |
[DebuggerDisplay("{value}", Name = "{key}")]
internal class KeyValuePairs
{
private IDictionary dictionary;
private object key;
private object value;
public KeyValuePairs(IDictionary dictionary, object key, object value)
{
this.value = value;
this.key = key;
this.dictionary = dictionary;
}
public object Key
{
get { return key; }
set
{
object tempValue = dictionary[key];
dictionary.Remove(key);
key = value;
dictionary.Add(key, tempValue);
}
}
public object Value
{
get { return this.value; }
set
{
this.value = value;
dictionary[key] = this.value;
}
}
}
[DebuggerDisplay("{DebuggerDisplay,nq}")]
[DebuggerTypeProxy(typeof(HashtableDebugView))]
class MyHashtable
{
public Hashtable hashtable;
public MyHashtable()
{
hashtable = new Hashtable();
}
private string DebuggerDisplay { get { return "Count = " + hashtable.Count; } }
private class HashtableDebugView
{
private MyHashtable myhashtable;
public HashtableDebugView(MyHashtable myhashtable)
{
this.myhashtable = myhashtable;
}
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public KeyValuePairs[] Keys
{
get
{
KeyValuePairs[] keys = new KeyValuePairs[myhashtable.hashtable.Count];
int i = 0;
foreach (object key in myhashtable.hashtable.Keys)
{
keys[i] = new KeyValuePairs(myhashtable.hashtable, key, myhashtable.hashtable[key]);
i++;
}
return keys;
}
}
}
}