Zagadnienia egzaminu 70-483 opisane w tej notatce:

  • Walidacja - sprawdzania poprawności danych wejściowych i użycie wyrażeń regularnych.
  • Debugowanie aplikacji - tworzenie i użycie dyrektyw procesora, użycie klas Debug i Trace o podgląd niepożądanych wartości.
  • Diagnostyka - użycie narzędzi takich jak profilerevent log i performance counter.

Sprawdzania poprawności danych wejściowych

Poniższa lista podsumowuje trzy etapy sprawdzania poprawności danych od najczęściej stosowanego i najmniej inwazyjnego do najrzadziej stosowanego i najbardziej inwazyjnego:

  1. Walidacja aktywacji przycisku - program może zignorować każdy przycisk który jest nieodpowiedni lecz pozwala na wszystkie które mogą prowadzić do poprawnych danych np znak “-” może rozpoczynać liczbę “-.123”. Opcjonalnie, można oznaczyć pole zawierające niewłaściwą wartość tak aby nie przerywała pracy użytkownika.
  2. Walidacja pola - Kiedy przechodzimy z zaznaczeniem do innego pola, program może sprawdzić poprawność jego wartości i oznaczyć niewłaściwą wartość. Program powinien wyświetlać informację, że wartość jest niepoprawna lecz nie powinien wymuszać jej natychmiastowego poprawienia na użytkowniku.
  3. Walidacja formularza - kiedy użytkownik próbuje zaakceptować cały formularz, program powinien sprawdzić wszystkie jego wartości i wyświetlić wiadomość informującą o zaistniałych błędach jeżeli istnieją. Jest to jedyne miejsce w którym program powinien wymusić poprawienie błędów na użytkowniku. Jest to również miejsce w którym program może sprawdzić poprawność pomiędzy różnymi zależnymi polami.

Użycie funkcji wbudowanych

Jedną z najprostszych metod walidacji danych jest sprawdzenie czy użytkownik wprowadził żądaną wartość. Jeżeli wartość byłą wprowadzona w polu tekstowym TextBox, to program może sprawdzić jej długość. 
Poniższy fragment kodu sprawdza czy zawartość pola tekstowego jest pusta:

if (emailTextBox.Text.Length == 0)
{
// The email field is blank. Display an error message.
//...
}

Dla innych typów kontrolek program musi sprawdzać inne właściwości kontrolki aby dowiedzieć się czy użytkownik dokonał wyboru wartości. Przykładowo dla ListBox lub ComboBox te właściwości to SelectedIndex i SelectedItem. Aby sprawdzić czy użytkownik wybrał wartość należy użyć sprawdzenia SelectedIndex == -1 lub SelectedItem == null
Kiedy wartość nie jest pusta, program może sprawdzać dodatkowo czy wartość ma sens. Przykładowo wartość test nie jest poprawnym adresem e-mail.

Język C# posiada wbudowane metody służące do sprawdzania poprawności danych wejściowych. Jednymi z najbardziej przydatnych metod są metody TryParse typów wbudowanych. Metody TryParse próbują parsować ciąg znaków i zwracają true jeżeli im się uda. 
Poniższy fragment kodu sprawdza czy pole tekstowe zawiera poprawną wartość waluty:

decimal cost;
if (!decimal.TryParse(costTextBox.Text,
NumberStyles.Currency,
CultureInfo.CurrentCulture,
out cost))
{
// Cost is not a valid currency value. Display an error message.
//...
}

Enumeracje NumberStyles i CultureInfo znajdują się w przestrzeni nazw System.Globalization.

Użycie metod klasy String

Klasa String posiada kilka metod które mogą być użyteczne przy sprawdzaniu poprawności danych wejściowych. 
Poniższa lista przedstawia większość użytecznych metod:

  • Contains - zwraca true jeżeli ciąg zawiera wybrany podciąg.
  • EndsWith - zwraca true jeżeli ciąg jest zakończony wybranym podciągiem.
  • IndexOf - zwraca pozycję wybranego podciągu w ciągu, opcjonalnie mozna rozpocząć przeszukiwanie od określonej pozycji.
  • IndexOfAny - zwraca pozycję któregokolwiek z wybranych znaków, opcjonalnie mozna rozpocząć przeszukiwanie od określonej pozycji.
  • IsNullOrEmpty - zwraca true jeżeli ciąg jest pusty lub null.
  • IsNullOrWhitespace - zwraca true jeżeli ciąg jest null, pusty lub zawiera tylko znaki spacji i tabulacji.
  • LastIndexOf - zwraca pozycję ostatniej lokalizacji podciągu w ciągu.
  • LastIndexOfAny - zwraca ostatniąpozycję któregokolwiek z wybranych znaków.
  • Remove - usuwa znaki z ciągu.
  • Replace - zastępuje podciąg lub znak innym.
  • Split - zwraca tablicę podciągów podzieloną wg wybranego zestawu znaków.
  • StartsWith - zwraca true jeżeli ciąg rozpoczyna się od wybranego podciągu.
  • Substring - zwraca podciąg na wyznaczonej pozycji.
  • ToLower - zwraca ciąg z zamienionymi wszystkimi znakami na małe litery.
  • ToUpper - zwraca ciąg z zamienionymi wszystkimi znakami na duże litery.
  • Trim - zwraca ciąg z usuniętymi białymi znakami na początku i na końcu ciągu.
  • TrimEnd - zwraca ciąg z usuniętymi białymi znakami na końcu ciągu.
  • TrimStart - zwraca ciąg z usuniętymi białymi znakami na początku ciągu.

Użycie wyrażeń regularnych

Wyrażenia regularne są elastycznym językiem służącym do odnajdywania wzorców w tekście. Wyrażenia regularne pozwalają programowi ustalić czy ciąg znaków pasuje do wzorca, znaleźć fragmenty w tekście które pasują do wzorca i zamienić części ciągu na nowe wartości. 
Klasa Regex z przestrzeni nazw System.Text.RegularExpressions służy do pracy z wyrażeniami regularnymi. 
Najczęściej używane metody klasy Regex:

  • IsMatch - zwraca true jeśli wyrażenie regularne dopasowuje ciąg.
  • Match - szuka pierwszego wystąpienia podciągu który pasuje do wyrażenia regularnego.
  • Matches - zwraca kolekcję z informacjami o wszystkich częściach ciągu które pasują do wyrażenia regularnego.
  • Replace - zastępuje niektóre lub wszystkie części ciągu które pasują do wyrażenia regularnego.
  • Split - dzieli ciąg do tablicy podciągów oddzielonych tekstem pasującym do wyrażenia regularnego.

Większość tych metod posiada przeciążone wersje. Większość z nich pobiera ciąg jako parametr i może opcjonalnie pobrać drugi parametr którym jest wyrażenie regularne. Jeżeli nie użyjemy parametru z wyrażeniem regularnym to zostanie użyte wyrażenie które podaliśmy w konstruktorze. 
Klasa Regex posiada również statyczną wersję tych metod które zawsze pobierają ciąg i wyrażenie regularne jako parametry.

Wyrażenia regularne są kombinacją literałów i znaków które mają specjalne znaczenie. Przykładowo sekwencja [a-z] oznacza, że obiekt Regex powinien dopasować każdy pojedynczy znak z zakresu od “a” do “z”. 
Wyrażenia regularne mogą zawierać również sekwencje znaków specjalnych zwanych escape sequences które reprezentują pewne specjalne wartości. Przykładowo sekwencja \b oznacza granicę wyrazu a \d oznacza jakąkolwiek cyfrę. 
Czasami program musi użyć znaku nawet jeżeli wygląda on jak znak specjalny. Przykładowo znak [ zwykle oznacza rozpoczęcie zakresu znaków. Jeżeli chcemy użyć dokładnie znaku [ musimy dołożyć do niego znak ucieczki jaki jest ** czyli użyć sekwencji [**.

Ucieczki znakowe

Lista najczęściej wykorzystywanych znaków ucieczki:

  • \t - dopasowuje tabulację.
  • \n - dopasowuje nową linię.
  • \nnn - dopasowuje znak z kodem ASCII podanym jako dwie lub trzy cyfry ósemkowe nnn.
  • \xnn - dopasowuje znak z kodem ASCII podanym jako dwie lub trzy cyfry szesnastkowe nnn.
  • \unnn - dopasowuje znak z kodem ASCII podanym jako cztery cyfry szesnastkowe nnnn.

Klasy znaków

Lista najczęściej wykorzystywanych klas znaków:

  • [chars] - dopasowuje znak do znaków w nawiasie, np [aeiou] dopasowuje małą pojedynczą samogłoskę.
  • [^chars] - dopasowuje pojedynczy znak do znaków które nie są w nawiasie.
  • [first-last] - dopasowuje znak do zakresu znaków w nawiasie, np [a-zA-Z] dopasowuje jakąkolwiek literę.
  • . - znak wieloznaczny, który dopasowuje dowolny pojedynczy znak z wyjątkiem \n.
  • \w - ekwiwalent [a-zA-Z_0-9].
  • \W - ekwiwalent [^a-zA-Z_0-9].
  • \s - dopasowuje pojedynczy biały znak.
  • \S - dopasowuje dowolny pojedynczy znak który nie jest białym znakiem.
  • \d - ekwiwalent [0-9].
  • \D - ekwiwalent [^0-9].

Kotwice

Kotwice reprezentują stan ciągu znaków. Lista najczęściej wykorzystywanych kotwic:

  • ^ - dopasowuje początek linii lub ciągu znaków.
  • $ - dopasowuje koniec linii lub ciągu znaków albo przed \n na końcu linii lub ciągu znaków.
  • \A - dopasowuje początek ciągu znaków.
  • \z - dopasowuje koniec ciągu znaków.
  • \Z - dopasowuje koniec ciągu znaków lub przed \n na końcu ciągu znaków.
  • \G - dopasowuje koniec poprzedniego dopasowania.

Konstrukcje grupujące

Konstrukcje grupujące pozwalają na zdefiniowanie grup dopasowanych fragmentów ciągu. Przykładowo w numerze telefonu 234-567-8901 można zdefiniować grupy przechowujące części 234, 567 i 8901. Program może później użyć tych grup w kodzie lub w tym samym wyrażeniu regularnym. 
Najczęściej wykorzystywanymi rodzajami grup są grupy numerowane i nazwane. 
Aby utworzyć grupę numerowaną należy zamknąć wyrażenie regularne w nawiasie okrągłym i nadać numer grupie zaczynając od 1, np (\w)\1
Aby utworzyć grupę nazwaną należy użyć składni (?subexpression), np (?\w).

Kwantyfikatory

Kwantyfikatory pozwalają na dopasowanie poprzedniego elementu określoną ilość razy, np \d{3} dopasowuje jakąkolwiek cyfrę trzy razy. Lista najczęściej wykorzystywanych kwantyfikatorów:

  • * - dopasowuje poprzedni element 0 lub więcej razy.
  • + - dopasowuje poprzedni element 1 lub więcej razy.
  • ? - dopasowuje poprzedni element 0 lub 1 raz.
  • {n} - dopasowuje poprzedni element n razy.
  • {n,} - dopasowuje poprzedni element n lub więcej razy.
  • {n,m} - dopasowuje poprzedni element od n do m razy.

Przemienność

Przemienność pozwala na użycie znaku | aby pozwolić na dopasowanie do jednego z dwóch wyrażeń, np wyrażenie (yes|no) dopasowuje albo yes alno no.

Opcje wyrażeń regularnych

Program może ustawić opcje wyrażeń regularnych na trzy sposoby. 
Pierwszym jest użycie dodatkowego parametru w metodzie obiektu klasy Regex. Parametr jest zdefiniowany jako enumerator RegexOptions
Drugim sposobem jest użycie składni (?options) w wyrażeniu regularnym. Znak - na początku opcji wyłącza wybrane opcje. 
Trzecim sposobem jest użycie składni (?options:subexpression) w wyrażeniu regularnym. Fragment subexpression oznacza część wyrażenia do której opcje mają być zastosowane. 
Lista możliwych opcji:

  • i - ignoruj wielkość liter.
  • m - wielowierszowy, znaki ^ i $ oznaczają początek i koniec linii.
  • s - jedna linia, znak . dopasowuje wszystkie znaki włącznie z \n.
  • n - jawne przechwytywanie, nie przechwytuje grup anonimowych.
  • x - ignoruj białe znaki we wzorcu i pozwala na użycie komentarzy po znaku #.

Poniższy przykład przedstawia wyrażenie regularne testujące czy podany numer jest poprawnym numerem telefonu w USA:

// Perform simple validation for a 7-digits US phone number.
privatevoidphone7TextBox_TextChanged(object sender, EventArgs e)
{
conststring pattern = @"^\d{3}-\d{4}$";
bool valid = false;
string text = phone7TextBox.Text;

if (text.Length == 0) valid = true;

if (Regex.IsMatch(text, pattern)) valid = true;

if (valid) phone7TextBox.BackColor = SystemColors.Control;
else phone7TextBox.BackColor = Color.Yellow;
}

Integralność Danych

To, że dane przeszły walidację nie zawsze oznacza, że są poprawne. Nawet jeżeli użytkownik wprowadził poprawne dane to mogą one zostać uszkodzone przez niepoprawne kalkulacje wykonywane przez system. Dodatkowymi metodami pozwalającymi na uniknięcie tego typu błędów są asercje i walidacja poprawności danych w bazie danych.

Użycie walidacji w bazie danych

Jeżeli program używa bazy danych to można w niej dodać kontrole i ograniczenia aby nie pozwolić na zapisanie nieprawidłowych danych. 
Silniki baz danych potrafią rozpoznać czy wartość jest pusta, posiada odpowiedni format, posiada unikalną wartość oraz potrafią rozpoznać relacje z innymi polami w tym samym lub innym rekordzie.

Użycie asercji

Innym sposobem na sprawdzenie integralności danych są asercje. Asercje są fragmentami kodu które deklarują pewne twierdzenie na temat danych i sprawdzają czy jest ono prawdziwe. 
Jednym ze sposobów stworzenia asercji jest użycie klauzuli if aby przetestować dane i zgłoszenie wyjątku jeżeli dane są niepoprawne, np:

if (!Regex.IsMatch(zip, @"^\d{5}$"))
throw new FormatException("ZIP code has an invalid format.");

Aby tworzyć prościej tego rodzaju asercje .NET Framework posiada klasę System.Diagnostic.Debug. Metoda Assert tej klasy testuje warunek i zgłasza wyjątek jeżeli nie jest prawdziwy. Poniższy fragment kodu jest ekwiwalentem poprzedniego z użyciem klasy Debug:

Debug.Assert(Regex.IsMatch(zip, @"^\d{5}$"));

Metoda Assert jest wykonywana tylko trybie debug programu.

Debugowanie

Środowisko programistyczne Visual Studio posiada narzędzi do interaktywnego debugowania aplikacji. Pułapki oraz podgląd wartości zmiennych za pomocą okna watches oraz zdolność do przechodzenie kodu krok po kroku pozwalają śledzić działanie aplikacji. Pułapki mogą dodatkowo posiadać warunki które musi spełnić kod aby zostały uruchomione, filtry oraz liczniki wykonania.

Dyrektywy procesora

Dyrektywy procesora mówią kompilatorowi języka C# jak ma wykonywać kod. Pozwalają na wykluczenie fragmentu kodu z kompilacji, zdefiniowanie symboli do użycia w zarządzanym kodzie oraz pogrupowanie fragmentów kodu.

Lista dyrektyw procesora:

  1. #define i #undef
    Dyrektywa #define definiuje symbol procesora lub warunkowy symbol kompilacji dla modułu który zawiera dyrektywę. Później można użyć dyrektyw #if lub #elif aby sprawdzić czy symbol został zdefiniowany. 
    Do symbolu nie można przypisać wartości. Można jedynie zdefiniować lub usunąć symbol oraz sprawdzić czy został zdefiniowany. 
    Można również użyć Visual Studio aby zdefiniowało symbole dla całego projektu. Aby tego dokonać we właściwościach projektu w zakładce Build definiujemy wybrane symbole w polu tekstowym Conditional Compilation Symbols
    Dyrektywa #undef usuwa symbol. 
    Obie dyrektywy #define i #undef mogą być używane jedynie na początku pliku więc dyrektywa #undef praktycznie służy tylko do usunięcia symboli zdefiniowanych na poziomie projektu.

  2. #if#elif#else i #endif
    Dyrektywy #if#elif#else i #endif działają jak instrukcje ifelse i else if języka C# lecz testują istnienie symboli procesora a nie wyrażenia logiczne. 
    Dyrektywy #if i #elif testują czy symbol został zdefiniowany. Jeżeli tak to kod który zawierają zostaje dołączony do kompilacji, jeżeli symbol nie istnieje to kod zostaje wyłączony z kompilacji. 
    Kod dyrektywy #else jest włączony do kompilacji jeżeli żaden z symboli z dyrektyw #if i #elif nie został zdefiniowany. 
    Dyrektywa #endif kończy blok dyrektyw, np:

// Debug levels. Level 2 gives the most information.
#define DEBUG1
//#define DEBUG2
privatevoidVerifyInternetConnections()
{
#if DEBUG2
// Display lots of debugging information.
//...
#elif DEBUG1
// Display some debugging information.
//...
#else
// Display minimal debugging information.
//...
#endif
// Verify the connections.
...
}

Można również testować równoczesne istnienie kilku symboli używając operatorów != i == oraz nawiasów jeżeli konieczna jest kolejność, np:

#if DEBUG1 == DEBUG2

Wartości true oraz false mogą być uzyte do reprezentowania istnienia symboli, poniższe trzy wyrażenia są sobie równoważne:

#if DEBUG1
#if DEBUG1 == true
#if DEBUG1 != false
  1. #warning i #error
    Dyrektywa #warning generuje ostrzeżenie które wyświetla się w oknie Error List w Visual Studio. Edytor kodu podkreśla ostrzeżenie falowaną zieloną linią. 
    Przykładowym użyciem jest oznaczenie kodu który jest przestarzały, np:
#if OLD_METHOD
#warning Using obsolete methodtocalculatefees.
//...
#else
//...
#endif

Dyrektywa #error jest podobna do #warning z tą różnicą, że zamiast ostrzeżenia generuje błąd i nie pozwala na poprawne zbudowanie projektu. Edytor w Visual Studio podkreśla błąd czerwoną falowaną linią.

  1. #line
    Dyrektywa #line pozwala zmienić numer linii w programie oraz opcjonalnie nazwę pliku która jest opisana w oknie ostrzeżeń, błędów i stosu. 
    Poniższy fragment kodu wyświetla śledzenie stosu ze zmienionym numerem linii i nazwą pliku:
#line 10000 "Geometry Methods"
Console.WriteLine("********** " + Environment.StackTrace);

Dyrektywa #line default przywraca linię zmienioną wcześniej przez dyrektywę #line
Dyrektywa #line hidden ukrywa linię przed debuggerem.

  1. #region i #endregion
    Dyrektywy #region i #endregion tworzą nazwany region w kodzie który może być zwijany i rozwijany. Nazwa regionu nie jest obowiązkowa, np:
#region Sales Routines
// Sales routines go here...
#endregion Sales Routines
  1. #pragma warning
    Dyrektywa #pragma daje specjalną instrukcję kompilatorowi, potencjalnie umożliwiając tworzenie nowych instrukcji preprocesora. Język C# pozwala na użycie dyrektyw #pragma warning i #pragma checksum
    Dyrektywa #pragma warning może włączyć i wyłączyć konkretne ostrzeżenie. Rozważmy następujący fragment kodu:
privateclass OrderItem
{
publicstring Description;
publicint Quantity = 0;
public decimal UnitPrice = 0;
}

Pole Description nie zostało zainicjowane i Visual Studio w czasie budowania projektu zgłosi następujące ostrzeżenie:

Field ‘Description’ is never assigned to, and will always have its default value null.

Poniższy fragment kodu pozwala na pominięcie tego ostrzeżenia:

privateclass OrderItem
{
#pragmawarning disable 0649
publicstring Description;
#pragmawarning restore 0649
publicint Quantity = 0;
public decimal UnitPrice = 0;
}
  1. #pragma checksum
    Dyrektywa #pragma checksum generuje sumę kontrolną dla pliku, np:
#pragma checksum "filename""{guid}""bytes"

Debug i Trace

Klasy Debug i Trace świadczą usługi, które wysyłają wiadomości do obiektów słuchacza. Domyślnie jedynym słuchaczem jest instancja klasy DefaultTraceListener która wysyła wiadomości do okna Output.

Klasa Debug jest ignorowana w wydaniach release. Dzieje się tak ponieważ do budowania projektu w wersji debug dodawany jest symbol DEBUG a w wersjach release nie. To domyślne zachowanie można zmienić we właściwościach budowania projektu. Można również stworzyć własne wersje budowania projektu które mogą również zawierać symbol DEBUG
Można również użyć dyrektyw #define i #undef aby zdefiniować symbol DEBUG. Przykładowo można zdefiniować symbol DEBUG w wybranym module aby móc użyć metody Debug.Assert.

Klasa Trace która jest również zdefiniowana w przestrzeni nazw System.Diagnostic, jest podobna do klasy Debug z tą różnicą, że jest kontrolowana przez symbol TRACE.

Klasy Debug i Trace posiadają wiele wspólnych metod. Poniżej lista najważniejszych z nich:

  • Assert - sprawdza warunek logiczny i zgłasza wyjątek jeżeli jest fałszywy.
  • Fail - emituje wiadomość błędu do obiektów nasłuchujących. Efekt jest podobny wyjątku zgłaszanego przez metodę Assert.
  • Flush - opróżnia wyjście do słuchaczy.
  • Indent - zwiększenie poziomu wcięcia o 1. pomaga to formatować wysyłane wiadomości.
  • Unindent - zmniejsza poziomu wcięcia o 1.
  • Write - zapisuje wiadomość do obiektów nasłuchujących.
  • WriteIf - zapisuje wiadomość do obiektów nasłuchujących jeżeli spełniony jest wyznaczony warunek logiczny.
  • WriteLine - zapisuje wiadomość do obiektów nasłuchujących i dodaje nową linię.
  • WriteLineIf - jeżeli spełniony jest wyznaczony warunek logiczny to zapisuje wiadomość do obiektów nasłuchujących i dodaje nową linię.

Listeners

Klasy Debug i Trace posiadają kolekcje Listeners w których znajduja się referencje do obiektów nasłuchujących. Domyślnie te kolekcje inicjowane są referencją do obiektu DefaultTraceListener
Aby skierować wyjście do innych lokalizacji należy dodać obiekt nasłuchu do kolekcji Listeners. Lista niektórych innych klas nasłuchu których można użyć:

  • ConsoleTraceListener - kieruje wyjście do okna Console.
  • EventLogTraceListener - kieruje wyjście do dziennika zdarzeń.
  • TextWriterTraceListener - kieruje wyjście do strumienia np FileStream. Pozwala to na zapis do dowolnego pliku.

Poniższy fragment kodu pokazuje jak stworzyć TextWriterTraceListener aby skierować wyjście do pliku TraceFile.txt:

using System.IO;
using System.Diagnostics;

privatevoidForm1_Load(object sender, EventArgs e)
{
// Create the trace output file.
Stream traceStream = File.Create("TraceFile.txt");

// Create a TextWriterTraceListener for the trace output file.
TextWriterTraceListener traceListener =
new TextWriterTraceListener(traceStream);
Trace.Listeners.Add(traceListener);

// Write a startup note into the trace file.
Trace.WriteLine("Trace started " + DateTime.Now.ToString());
}

W czasie działania można dodawać wiadomości przez obiekt Trace. Poniższy fragment kodu pokazuje jak dodawać informacje podczas przetwarzania zamówienia:

privatevoid processOrderButton_Click(object sender, EventArgs e)
{
// Log an order processing message.
Trace.WriteLine("Processing order");

// Log the order's data.
Trace.Indent();
Trace.WriteLine("CustomerId: " + CustomerId);
Trace.WriteLine("OrderId: " + OrderId);
Trace.WriteLine("OrderItems:");
Trace.Indent();
foreach (OrderItem item in OrderItems)
Trace.WriteLine(item.ToString());
Trace.Unindent();
Trace.WriteLine("ShippingAddress: " + ShippingAddress);
Trace.Unindent();
// Process the order.
//...
}

Kiedy program dobiega końca może opróżnić buforowany tekst do pliku:

privatevoidForm1_FormClosing(object sender, FormClosingEventArgs e)
{
// Flush the trace output.
Trace.WriteLine("Trace stopped " + DateTime.Now.ToString());
Trace.Flush();
}

Programowanie plików bazy danych debugowania

Kiedy budujemy projekt w trybie bebug to Visual Studio tworzy plik bazy danych programu zawierający informacje o programie potrzebne do debugowania. 
Aby kontrolować ilość informacji którą VS dodaje do pliku PDB należy użyć właściwości projektu. W zakładce Build wybieramy Advanced Build Settings i wybieramy z listy rozwijanej wartość fullpdb-only lub none
Domyślnie wybrana jest opcja full tworząca w pełni debugowany program. Opcja pdb-only jest domyślna dla trybu release która potrafi wskazać gdzie wystąpiły wyjątki. Opcja none sprawia, że VS nie tworzy pliku bazy danych.

Instrumentacja aplikacji

Instrumentacja aplikacji oznacza dodanie do aplikacji funkcjonalności umożliwiających jej analizowanie. Zwykle jest to dodanie monitorowania wydajności, zapisu błędów i śledzenia wykonywania programu. Dzięki dobrej instrumentacji można zidentyfikować wąskie gardła w aplikacji bez krokowego śledzenia jej przebiegu za pomocą debugera.

Tracing

Tracing jest procesem instrumentacji programu polegającym na śledzeniu co jest wykonywane w aplikacji. Zwykle śledzenie odbywa się z pomocą klas Debug i Trace. Dodatkowo do zapisywanych informacji można dodać czas i datę wykonywania aby można było zbadać jak program zachowuje sie w różnym czasie.

Event Logs

Logowanie jest procesem instrumentacji programu polegającym na zapisywaniu kluczowych zdarzeń do dziennika. 
Zapis do dziennika zdarzeń może odbywać się na różne sposoby. Można użyć klas Debug i Trace, choć ich przeznaczeniem jest raczej śledzenie. Można użyć zapisu do plik w odpowiednich sekcjach. Można użyć zewnętrznej biblioteki jak Log4Net lub NLog. Można również zapisywać zdarzenia do dziennikasystemowego. 
Poniższy fragment kodu zapisuje zdarzenia do dziennika systemowego:

using System.Diagnostics;
//...
// Write an event log entry.
privatevoidwriteButton_Click(object sender, EventArgs e)
{
string source = sourceTextBox.Text;
stringlog = logTextBox.Text;
string message = eventTextBox.Text;
int id = int.Parse(idTextBox.Text);

// Create the source if necessary. (Requires admin privileges.)
if (!EventLog.SourceExists(source))
EventLog.CreateEventSource(source, log);

// Write the log entry.
EventLog.WriteEntry(source, message,
EventLogEntryType.Information, id);
MessageBox.Show("OK");
}

Zwykle użyteczna jest możliwość konfiguracji ilości logowanych zdarzeń. Pozwala to na zmniejszenie ilości zapisywanych informacji w środowiskach produkcyjnych aby zapis do dziennika nie wpływał na wydajność aplikacji.

Profiling

Profilowanie jest procesem zbierania informacji o programie służącym do późniejszego studiowania jego szybkości, pamięci, użycia zasobów i innych charakterystyk wydajnościowych. Istnieją dwa sposoby profilowania programu, za pomocą profilera lub poprzez instrumentowanie programu ręcznie.

Użycie profilera

Automatyczne profilery używają kilku podejść do profilowania aplikacji. Niektóre instrumentują kod źródłowy dodając wyrażenia badające czas do niektórych lub wszystkich metod programu. Inne instrumentują kod skompilowany. Jeszcze inne używają próbkowania CPU. 
Visual Studio w edycjach Premium i Ultimate posiadają narzędzia służące do badania wydajności aplikacji.
Narzędzia te uruchamiamy wybierając w VS z menu Analyze i Launch Performance Wizard. Okno czarodzieja wygląda tak:
1
Opcja próbkowanie CPU cyklicznie sprawdza stan programu aby zobaczyć co jest wykonywane. Pozwala to zdiagnozować które procedury są najbardziej obciążające dla CPU. 
Opcja instrumentacji może dostarczyć dokładniejszych informacji lecz dodaje instrumentacje do skopmilowanego kodu co może spowolnić działanie aplikacji. 
Opcja .NET Memory Allocation używa próbkowania do zbierania informacji o uzyciu pamięci. 
Opcja Resource Contention Data jest używana do badania współbieżności w aplikacjach wielowątkowych.
Opcja próbkowania CPU generuje wyniki podobne do poniższych:
2

Profilowanie ręczne

Program można profilować ręcznie poprzez wstawianie do kodu źródłowego fragmentów kodu zapisujących stan aplikacji oraz aktualny czas i czas wykonania. 
Poniższy fragment kodu używa klasy Stopwatch do badania czasu wykonania:

privatevoidPerformCalculations()
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();

// Perform the calculations here.
//...

Console.WriteLine("Time: " +
stopwatch.Elapsed.TotalSeconds.ToString("0.00") +
" seconds");
}

Innym sposobem ręcznego profilowania jest użycie liczników wydajności.

Performance Counters

Liczniki wydajności śledzą system operacyjny, aby dać wyobrażenie o aktywności komputera. 
Przykładowo przypuśćmy, że program procesujący zdjęcia skanuje folder w każdej minucie. Jeżeli odnajdzie w folderze zdjęcie, pobiera je, procesuje w jakiś sposób i przenosi do innego folderu. Można użyć liczników wydajności aby śledzić procesowanie każdego pliku. Później można użyć narzędzia systemowego Performance Monitor do zobrazowania zmian licznika podczas procesowania programu.

Liczniki wydajności można tworzyć za pomocą kodu (więcej na msdn), lub za pomocą Visual Studio.
VS otwieramy okno Server Explorer, rozwijamy drzewo i wybieramy Performance Counters w którego menu kontekstowym dodajemy nową kategorię poprzez Create New Category.
3
Aby użyc licznika wydajności w programie należy utworzyć obiekt klasy PerformanceCounter z przestrzeni nazw System.Diagnostics. Poniższy fragment kodu demonstruje użycie utworzonego na zdjęciu licznika:
private PerformanceCounter totalImages, imagesPerSecond;

privatevoidForm1_Load(object sender, EventArgs e)
{
totalImages = new PerformanceCounter();
totalImages.CategoryName = "ImageProcessor";
totalImages.CounterName = "Images processed";
totalImages.MachineName = ".";
totalImages.ReadOnly = false;

imagesPerSecond = new PerformanceCounter();
imagesPerSecond.CategoryName = "ImageProcessor";
imagesPerSecond.CounterName = "Images per second";
imagesPerSecond.MachineName = ".";
imagesPerSecond.ReadOnly = false;
}

Kiedy mamy już utworzony obiekt licznika to program może inkrementować go kiedy wykonywana jest przez nas pożądana akcja. 
Załóżmy, że program procesujący zdjęcia skanuje folder cyklicznie sprawdzając czy zawiera zdjęcia. Kiedy znajdzie plik to wywołuje następującą metodę:

privatevoidProcessImageFile(string filename)
{
// Process the file.
//...

// Increment the performance counters.
totalImages.Increment();
imagesPerSecond.Increment();
}
Aby zobaczyć efekt działania należy uruchomić systemowe narzędzie Performance Monitor.