Zagadnienia egzaminu 70-483 opisane w tej notatce:

  • Konwersja wartości z jednego typu danych do drugiego.
  • Poszerzenie, zwężenie, ukryta i jawna konwersja typów.
  • Rzutowanie typów.
  • Manipulowanie ciągami znaków.
  • Formatowanie wartości.

Konwersja pomiędzy typami

Wielokrotnie programy muszą konwertować wartości z jednego typu do drugiego. Przykładowo program do tworzenia wykresów używa funkcji Math.Sin i Math.Cos do wyliczania wartości dla wykresu po czym używa obiektu klasy Graphics i metody DrawLines do rysowania wykresu. Pierwsze dwie metody używają wartości typu double natomiast trzecia reprezentuje punkty za pomocą typu float lub int
Konwersja między typami w języku C# może odbywać się na kilka różnych sposobów:

  • Poprzez rzutowanie (cast)
  • Poprzez użycie operatora as
  • Poprzez parsowanie (parse)
  • Użycie System.Convert
  • Użycie System.BitConverter

Konwersja poszerzająca i zwężająca

Konwersja poszerzająca polega na zmianie typu źródłowego na typ o większym zakresie który na pewno może posiadać każdą wartość zapisaną w typie źródłowym. W przypadku konwersji zwężającej typ docelowy nie pomieści wszystkich wartości typu źródłowego. Przykładowo typ int może pomieścić wartości z zakresu –2,147,483,648 i 2,147,483,647. Typshort może pomieścić wartości z zakresu –32,768 i 32,767. Konwersja z typu short do int jest konwersją rozszerzającą, natomiast konwersja z typu int do typu short jest konwersją zwężającą. 
Konwersja zwężająca może zakończyć się niepowodzeniem jeżeli wartość nie mieści się w zakresie typu docelowego konwersji. Domyślnie język C# nie zgłosi wyjątku w czasie konwersji zwężającej dla typów całkowitych lub zmiennoprzecinkowych. Dla liczb całkowitych wynik zostanie obcięty do najwyższej lub najniższej możliwej wartości. W przypadku liczb zmiennoprzecinkowych ustawiona zostanie wartość nieskończoności. 
Można wymusić wyrzucenie wyjątku przez C# na kilka sposobów. 
Dla typów całkowitych pierwszym z nich jest użycie bloku checked np.

checked
{
int big = 1000000;
short small = (short)big;
}

Blok checked wyrzucie wyjątek OverflowException jeżeli konwersja się nie powiedzie. 
Drugim sposobem jest ustawienie pola Check For Arithmetic Overflow w opcjach zaawansowanych budowania projektu. 
Dla typów zmiennoprzecinkowych trzeba jawnie sprawdzić czy wynik nie jest nieskończonością. Typ float posiada metody sprawdzające IsInfinity i NegativeInfinity, oto przykład:

double big = -1E40;
float small = (float)big;
if (float.IsInfinity(small)) thrownew OverflowException();

Konwersja jawna i niejawna

Konwersja niejawna (implicit) polega na tym, że program automatycznie konwertuje wartość z jednego typu danych do innego bez żadnych dodatkowych instrukcji mówiących o tym, że ma dokonać konwersji. konwersja jawna (explicit) używa dodatkowych operatorów lub metod jak np operator cast lub metoda parse żeby jawnie powiedzieć programowi jak ma dokonać konwersji typów danych. 
Ponieważ konwersja zwężająca może prowadzić do utraty danych to C# nie wykonuje takiej konwersji automatycznie, więc nie pozwoli na niejawną konwersję zwężającą. Konwersja która może powodować utratę danych musi być deklarowana jawnie. 
Ponieważ konwersja rozszerzająca zawsze kończy się powodzeniem to program w C# może dokonywać jej niejawnie. 
Poniższy kod prezentuje przykłady konwersji jawnej i niejawnej:

// Narrowing conversion so explicit conversion is required.
double value1 = 10;
float value2 = (float)value1;
// Widening conversion so implicit conversion is allowed.
int value3 = 10;
long value4 = value3;

Dla typów referencyjnych konwersja do bezpośredniej lub pośredniej klasy przodka lub interfejsu jest konwersją rozszerzającą więc program może jej dokonać jawnie.

Rzutowanie

Operator rzutowania mówi kompilatorowi bezpośrednio, że chcemy konwertować wartość do danego typu danych. Aby rzutować zmienną do określonego typu umieszczamy nazwę typu do którego chcemy konwertować w nawiasie okrągłym tuż przed zmienną do konwersji. Przykład wykorzystania operatora rzutowania:

double value1 = 10;
float value2 = (float)value1;

Operator rzutowania może prowadzić do obcięcia wartości np przy rzutowaniu wartości zmiennoprzecinkowej na typ całkowity. 
Ponieważ konwersja typów referencyjnych do bezpośredniej lub pośredniej klasy przodka lub interfejsu jest konwersją rozszerzającą to program może wykonywać ją niejawnie np. jeżeli klasa Employee dziedziczy z klasy Person:

Employee employee1 = new Employee();
Person person1 = employee1;

Konwersja typów referencyjnych do bezpośredniej lub pośredniej klasy przodka lub interfejsu nie zmienia wartości zmiennej tylko jej typ. Konwersja z typu Person do typu Employee jest konwersją zwężającą więc wymaga operatora rzutowania np:

Employee employee1 = new Employee();
Person person1 = employee1;
Person person2 = new Employee();
// Allowed because person1 is actually an Employee.
Employee employee2 = (Employee)person1;

Operator tego typu spowoduje, że dany kod się skompiluje, lecz jeżeli wartość nie jest aktualnie odpowiedniego typu to program zgłosi wyjątek InvalidCastException. Przykładowo poniższy kod zgłosi wyjątek:

Person person2 = new Person();
// Not allowed because person2 is a Person but not an Employee.
Employee employee3 = (Employee)person2;

Ponieważ programy często potrzebują konwersji pomiędzy kompatybilnymi typami referencyjnymi to C# umożliwia użycie dwóch operatorów które ułatwiają taką konwersję, są to operatory is i as.

Operator “is”

Operator is określa czy obiekt jest zgodny z określonym typem. Poniższy kod sprawdza czy zmienna user jest typuEmployee:

if (user is Employee)
{
// Do something with the Employee...
}

Operator is zwraca true jeżeli obiekt jest kompatybilny z typem tzn, że zwróci prawdę jeżeli obiekt user jest typu który dziedziczy z klasy Employee.

Operator “as”

Słowo kluczowe as służy do rzutowania typów. Wyrażenie object as Class zwraca obiekt skonwertowany na wyznaczoną klasę jeżeli obiekt ten jest kompatybilny z tą klasą. Jeżeli obiekt nie jest kompatybilny z klasą to rezultatem będzie null np.

Employee emp = user as Employee;
if (emp != null)
{
// Do something with the Employee...
}

Jedną z sytuacji, w której operator as jest szczególnie przydatna jest sytuacja, gdy wiesz, że zmienna odnosi się do obiektu określonego typu np obsługa zdarzenia CheckedChanged.

// Make the selected RadioButton red.
privatevoidMenuRadioButton_CheckedChanged(object sender, EventArgs e)
{
RadioButton rad = sender as RadioButton;
if (rad.Checked) rad.ForeColor = Color.Red;
else rad.ForeColor = SystemColors.ControlText;
}

Rzutowanie tablic

W przypadku rzutowania tablic obowiązują te same reguły co w przypadku rzutowania typów wartości i referencyjnych. Załużmy, że klasa Employee dziedziczy z klasy Person a klasa Manager dziedziczy z klasy Employee. poniższy kod demonstruje reguły rzutowania tablic:

// Declare and initialize an array of Employees.
Employee[] employees = new Employee[10];
for (int id = 0; id < employees.Length; id++)
{
employees[id] = new Employee(id);
}

// Implicit cast to an array of Persons.
// (An Employee is a type of Person.)
Person[] persons = employees;

// Explicit cast back to an array of Employees.
// (The Persons in the array happen to be Employees.)
employees = (Employee[])persons;

// Use the is operator.
if
(persons is Employee[])
{
// Treat them as Employees.
}

// Use the as operator.
employees = persons as Employee[];

// After this as statement, managers is null.
Manager[] managers = persons as Manager[];

// Use the is operator again, this time to see
// if persons is compatible with Manager[].
if
(persons is Manager[])
{
// Treat them as Managers.
}

// This cast fails at run time because the array
// holds Employees not Managers.
managers = (Manager[])persons;

Rzutowanie nie tworzy nowej tablicy. Aby utworzyć kopię można użyć metody Array.Copy

Wiedząc, że tablica persons jest aktualnie referencją do tablicy obiektów klasy Employee, to jasne jest, że poniższe wyrażenie zgłosi wyjątek typu ArrayTypeMismatchException:

persons[0] = new Person(0);

Rzutowanie tablicy na nowy typ nie powoduje, że elementy tablicy stają się obiektami tego typu.

Konwertowanie wartości

Rzutowanie pozwala na konwersję pomiędzy kompatybilnymi typami. Czasami jednak potrzebujemy konwersji pomiędzy typami które nie są ze sobą kompatybilne np tekstowy ciąg znaków “10” do typu całkowitego. W takim przypadku rzutowanie zawiedzie. 
Aby dokonać konwersji pomiędzy niekompatybilnymi typami trzeba użyć klas lub metod pomocniczych których dostarcza.NET Framework, są to:

  • Metody parsujące
  • Klasa System.Convert
  • Klasa System.BitConverter

Metody parsujące

Każdy z typów prostych języka C# posiada metodę Parse która konwertuje ciąg znaków do tego typu danych. Jeżeli wartość wejściowa jest w nierozpoznawalnym formacie to zgłoszony zostaje wyjątek np: FormatException w przypadkubool.Parse(“yes”) lub OverflowException jeżeli liczba jest z poza zakresy typu do którego jest konwertowana. 
Każda z klas posiada również metodę TryParse która zwraca true jeżeli konwersja się powiedzie i false w przeciwnym wypadku. Skonwertowana wartość zwracana jest jako zmienna wyjściowa którą wstawiono jako argument metody. 
Przykład parsowania wartości wprowadzonych do pola tekstowego na zmienną typu całkowitego:

int quantity;
try
{
quantity = int.Parse(quantityTextBox.Text);
}
catch
{
quantity = 1;
}
int weight;
if (!int.TryParse(weightTextBox.Text, out weight)) weight = 1;

Jeżeli to możliwe to zaleca się unikania parsowania ponieważ dla niektórych typów może być ono skomplikowane. 
Parsowanie daty i czasu działa dobrze jeżeli parsowany ciąg znaków jest dobrze sformatowany np dla j. angielskiego:

DateTime.Parse("3:45 PM April 1, 2014").ToString()
DateTime.Parse("1 apr 2014 15:45").ToString()
DateTime.Parse("15:45 4/1/14").ToString()
DateTime.Parse("3:45pm 4.1.14").ToString()

Parsowanie walut w domyślny sposób jest skomplikowane. Symbol waluty nie jest domyślnie obsługiwany więc poniższa instrukcja zgłosi wyjątek SyntaxException:

decimal amount = decimal.Parse("$123,456.78");

Można do metody parse dodać kolejny parametr umożliwiający użycie symboli walut. Jego wartość jest kombinacją wartości zdefiniowanych w enumeratorze System.Globalization.NumberStyles
Przykładowo decimal.Parse używa domyślnie stylów pozwalających na użycie separatora dla tysięcy i liczb po przecinku. 
Przykład umożliwiający użycie symboli walut:

decimal amount = decimal.Parse("$123,456.78",
NumberStyles.AllowCurrencySymbol |
NumberStyles.AllowThousands |
NumberStyles.AllowDecimalPoint);

Parsowanie uwzględnia również lokalizację i próbuje interpretować argumenty dla narodowości i języka w którym program jest uruchamiany. 
Przykładowo we Francji metoda int.Parse skonwertuje wartość “123 456,78” lecz zgłosi wyjątek dla “123.456,78.”. 
Wszystkie literały w języku C# powinny używać formatowania U.S. English.

System.Convert

Klasa System.Convert posiada ponad 300 metod służących do konwersji pomiędzy typami (wliczając metody przeciążone). Przykładowo metoda System.Convert.ToInt32 konwertuje wartość do typu int. Metoda ToInt32 posiada przeciążone wersje przyjmujące jako argument różne typy danych. 
Metody konwertujące do typów całkowitych używają tzw “zaokrąglenia bankiera” co oznacza, że zaokrąglają wartość do najbliższej wartości całkowitej. Jeżeli wartość jest równo w połowie np 0.5 to zaokrąglenie odbywa się do najbliższej liczby parzystej (przykładowo ToInt16(9.5) i ToInt16(10.5) zwrócą 10).

Klasa System.Convert posiada również metodę która konwertuje wartość do nowego typu ustalanego poprzez parametr w czasie wykonania np (int)Convert.ChangeType(5.5, typeof(int)) zwróci 6.

System.BitConverter

Klasa System.BitConverter posiada metody pozwalające na konwersję do tablicy bajtowej i z tablicy bajtowej. MetodaGetbytes zwraca tablicę bajtów reprezentującą wartość która została wprowadzona jako argument. Przykładowo jeżeli wstawimy wartość typu int to zwrócona zostanie tablica zawierająca cztery bajty reprezentujące tą wartość. 
Klasa System.BitConverter posiada również metody jak np ToInt32 i ToSingle które konwertują wartości w tablicy bajtowej do żądanego typu danych.

Pakowanie i rozpakowywanie typów wartości (boxing and unboxing)

Pakowanie (boxing) jest procesem konwersji typów wartości do obiektu lub interfejsu który jest obsługiwany przez ten typ. Rozpakowywanie jest procesem odwrotnym. 
Przykładowo poniższy kod tworzy obiekt który odnosi się do wartości całkowitej:

// Declare and initialize integer i.
int i = 10;
// Box i.
object iObject = i;

Zmienna iObject jest obiektem który odnosi się do wartości 10. 
Pakowanie i rozpakowywanie zajmuje więcej czasu niż przypisanie jednej zmiennej typu wartości do innej więc powinno sie tego unikać jeżeli to możliwe. 
Zwykle pakowanie i rozpakowywanie jest wykonywane automatycznie. często dzieje się to gdy wywołujemy metodę która oczekuje jako parametru obiektu a otrzymuje wartość np.

int i = 1337;
Console.WriteLine(string.Format("i is: {0}", i));

Dodatkowo pakowanie i rozpakowywanie posiada delikatny efekt uboczny który prowadzi do mylącego kodu np:

// Declare and initialize integer i.
int i = 10;
// Box i.
object iObject = i;
// Change the values.
i = 1;
iObject = 2;
// Display the values.
Console.WriteLine(i);
Console.WriteLine(iObject);

Wynikiem tego kodu będą liczby 1 i 2. Zmienna iObject wydaje się być referencją do zmiennej i lecz tak naprawdę są to dwie różne wartości.

Zapewnienie interoperacyjności z kodem niezarządzalnym

Interoperacyjność pozwala programowi w C3 na użycie klas udostępnionych przez kod niezarządzalny który nie został napisany pod kontrolą CLR (Common Language Runtime). Przykładami kodu niezarządzanego są ActiveX i Win32 API
Dwoma najpopularniejszymi technikami pozwalającymi na używanie kodu niezażądzanego są COM Interop i Platform invoke (P/invoke).

P/invoke

Aby użyć kodu niezarządzanego z poziomu P/invoke należy załączyć atrybut DllImport z przestrzeni nazwSystem.Runtime.InteropServices który definiuje niezarządzane metody które będą używane przez zarządzany program. 
DllImport przyjmuje parametry które mówią o cechach metod niezarządzanych. parametry te zawierają nazwę biblioteki DLLktóra zawiera metodę, zestaw znaków używanych przez metodę (Unicode lub ANSI) i punkt wejścia w DLL używany przez metodę. 
Program stosuje atrybut jako deklarację metody static extern. Deklaracja zawiera parametry których używa metoda oraz zwracany typ. Deklaracja ta powinna znajdować się w klasie. Przykład użycia DllImport:

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespaceShortPathNames
{
publicpartialclassForm1 : Form
{
publicForm1()
{
InitializeComponent();
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
staticexternuintGetShortPathName(string lpszLongPath, char[] lpszShortPath, int cchBuffer);
// Get the long file name.
string longName = fileTextBox.Text;
// Allocate a buffer to hold the result.
char[] buffer = newchar[1024];
long length = GetShortPathName(longName, buffer, buffer.Length);
// Get the short name.
string shortName = newstring(buffer);
shortNameTextBox.Text = shortName.Substring(0, (int)length);
}
}

Do lepszej kontroli konwersji wartości pomiędzy kodem zarządzanym i niezarządzanym służy atrybut MarshalAs np:

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError=true)]
static extern uint GetShortPathName(
[MarshalAs(UnmanagedType.LPTStr)] string lpszLongPath,
[MarshalAs(UnmanagedType.LPTStr)] StringBuilder lpszShortPath,
uint cchBuffer);

Typy dynamiczne i COM Interop

COM Interop umożliwia inny sposób współpracy kodu zarządzanego z niezarządzanym. Aby użyć COM Interop należy dodać referencję do odpowiedniej biblioteki poprzez zakładkę COM w oknie Visual Studio. Dodanie referencji mówi programowi wiele o kodzie niezarządzanym, możemy np podejrzeć obiekty biblioteki w oknie Object Browser i mamy dostęp do niektórych komponentów biblioteki z IntelliSense. Niestety nie wszystkie typy biblioteki są zawsze rozpoznawane. 
Od wersji 4.0 języka C# wprowadzony został specjalny typ danych dynamic którego można użyć w takich sytuacjach. Jest to statyczny typ danych, lecz jego prawdziwy typ jest wyznaczany w czasie uruchomienia. W czasie kompilacji typ dynamicnie jest sprawdzany i nie zwraca błędów składni ani niedopasowania typów. 
C# uznaje obiekty zdefiniowane przez kod niezarządzany COM jako typ dynamic
Przykład użycia dynamic i biblioteki Microsoft.Office.Interop.Excel jako biblioteki COM Interop:

// Open the Excel application.
Excel._Application excelApp = new Excel.Application();
// Add a workbook.
Excel.Workbook workbook = excelApp.Workbooks.Add();
Excel.Worksheet sheet = workbook.Worksheets[1];
// Display Excel.
excelApp.Visible = true;
// Display some column headers.
sheet.Cells[1, 1].Value = "Value";
sheet.Cells[1, 2].Value = "Value Squared";
// Display the first 10 squares.
for (int i = 1; i <= 10; i++)
{
sheet.Cells[i + 1, 1].Value = i;
sheet.Cells[i + 1, 2].Value = (i * i).ToString();
}
// Autofit the columns.
sheet.Columns[1].AutoFit();
sheet.Columns[2].AutoFit();

Przykład bardziej specyficznego dla języka C# kodu wykorzystującego typ dynamic:

// Make an array of numbers.
int[] array1 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// This doesn't work because array1.Clone is an object.
//int[] array2 = array1.Clone();
// This works.
int[] array3 = (int[])array1.Clone();
array3[5] = 55;
// This also works.
dynamic array4 = array1.Clone();
array4[6] = 66;
array4[7] = "This won't work";

Manipulowanie ciągami znaków

Typ danych String jest inny niż wszystkie inne typy. Programy traktują je zwykle jak inne typy wartości ale klasa String jest bardzo skomplikowana. Typ string jest aliasem dla klasy System.String
.NET Framework znaki reprezentowane są jako Unicode w wersji UTF-16. Format ten zapisuje każdy znak jako 16 bitów. Typ String jest obiektem który używa ciągu znaków do zapisania jakiegoś tekstu. Cechą typu string jest to że jego wartości są niezmienne (immutable). Oznacza to, że treść ciągu znaków zapisanych jako String nie może być zmieniona po utworzeniu obiektu. Zamiast tego metody modyfikujące ciągi znaków jak np Replace zwracają nowy obiekt Stringzawierający zmodyfikowaną wartość. 
W celu oszczędzania pamięci CLR utrzymuje tablicę o nazwie intern pool która przetrzymuje pojedynczą referencję do każdego unikalnego tekstu używanego przez program. Każda zmienna typu String odwołująca się do jakiegoś fragmentu tekstu jest referencją do intern pool
Wymaga to pewnego narzutu obliczeniowego dlatego praca z ciągami znaków nie jest tak szybka jak z typami wartości. Jeżeli program musi dokonać wielu scaleń tekstów a każde tworzy nowy obiekt String to w rezultacie zajmuje to dużo czasu. W takim przypadku użycie klasy StringBuilder może poprawić wydajność.

Konstruktory klasy String

Trzy najbardziej znane metody inicjalizujące zmienną typu String to:

  • ustawienie jako literału.
  • ustawienie jako tekstu wpisanego do kontrolki użytkownika.
  • ustawienie jako wyniku operacji na tekście. 
    Ostatnia z metod zawiera metody formatujące zmienne jako ciąg znaków np za pomocą metody ToString lubString.Format
    Oprócz tych metod, klasa String posiada kilka konstruktorów które mogą być czasami użyteczne:
  • Konstruktor inicjalizujący obiekt z tablicy znaków.
  • Konstruktor inicjalizujący obiekt z części tablicy znaków, pobierający dodatkowe parametry startowej pozycji w tablicy i długości ciągu.
  • Konstruktor pobierający znak i liczbę powtórzeń tego znaku.

Właściwości i pola klasy String

Klasa String posiada trzy pola i właściwości: EmptyLength i indexer który jest tylko do odczytu. 
Pole Empty zwraca obiekt reprezentujący pusty ciąg znaków równy literałowi “”. 
Właściwość Length zwraca długość ciągu znaków. 
Indexer który jest tylko do odczytu zwraca znaki typu char ciągu typu string. Dzięki temu możemy odczytać znak który jest na konkretnej pozycji ciągu.

Metody klasy String

Większość przydatnych statycznych metod klasy String:

  • Compare - Porównuje dwa ciągi znaków i zwraca -1, 0 lub 1 co oznacza, że pierwszy porównywany ciąg jest przed drugim, jest równy lub jest za drugim ciągiem w kolejności alfabetycznej. Metody przeciążone pozwalają na ustanowienie zasad porównywania i wybór języka porównywania.
  • Concat - Scala ciągi znaków podane na wejściu jako tablica lub w metodzie przeciążonej jako wiele argumentów typustring.
  • Copy - zwraca kopię ciągu znaków.
  • Equals - zwraca true jeżeli ciągi są takie same.
  • Format - używana do generowania sformatowanego tekstu. Przyjmuje jako parametry listę ciągów.
  • IsNullOrEmpty - sprawdza czy wartość ciągu jest pusta lub równa null.
  • IsNullOrWhiteSpace - zwraca true jeżeli ciąg jest pusty, równy null lub zawiera białe znaki.
  • Join - złącza ciągi tablicy oddzielając je w docelowym ciągu znakiem separacji podawanym jako pierwszy parametr wejściowy.

Większość przydatnych metod instancji klasy String:

  • Clone - zwraca nową referencję do obiektu String.
  • CompareTo - porównuje ciąg z innym i zwraca -1 jeżeli jest przed, 0 jeżeli jest równy i 1 jeżeli jest za w kolejności alfabetycznej.
  • Contains - zwraca true jeżeli ciąg zawiera wybrany podciąg.
  • CopyTo - kopiuje wybraną ilość znaków od wybranej pozycji początkowej do tablicy znaków.
  • EndsWith - zwraca true jeżeli ciąg zakończony jest wybranym podciągiem.
  • Equals - zwraca true jeżeli ciąg jest równy innemu ciągowi.
  • IndexOf - zwraca indeks pierwszego wystąpienia znaku lub podciągu w ciągu. Parametry pozwalają także zdefiniować pozycję startową i końcową przeszukiwania oraz opcje porównywania.
  • LastIndexOf - zwraca indeks ostatniego wystąpienia znaku lub podciągu w ciągu. Parametry pozwalają także zdefiniować pozycję startową i końcową przeszukiwania oraz opcje porównywania.
  • Insert - wstawia ciąg na wyznaczonej pozycji.
  • PadLeft - zwraca ciąg wypełniony z lewej strony wybraną liczbą wyspecyfikowanych znaków.
  • PadRight - zwraca ciąg wypełniony z prawej strony wybraną liczbą wyspecyfikowanych znaków.
  • Remove - usuwa znaki od wyznaczonej pozycji początkowej do wyznaczonej pozycji końcowej.
  • Replace - zamienia wszystkie wystąpienia znaku lub podciągu na inny znak lub ciąg.
  • Split - rozdziela ciąg znaków na podciągi oddzielane wyznaczonymi znakami rozdzielającymi i zwraca tablicę podciągów.
  • StartWith - zwraca true jeżeli ciąg zaczyna się od wybranego podciągu.
  • Substring - zwraca podciąg zaczynający się w wyznaczonej pozycji i posiadający wyznaczoną ilość znaków.
  • ToCharArray - zwraca tablicę znaków.
  • ToLower - zwraca kopię ciągu skonwertowana na małe znaki.
  • ToUpper - zwraca kopię ciągu skonwertowana na duże znaki.
  • Trim - zwraca kopię ciągu z usuniętymi białymi znakami na początku i na końcu ciągu.
  • TrimEnd - zwraca kopię ciągu z usuniętymi białymi znakami na końcu ciągu.
  • TrimStart - zwraca kopię ciągu z usuniętymi białymi znakami na początku ciągu.

Klasy obsługujące ciągi znaków

Niezwykły sposób w jaki ciągi znaków są przechowywane czyni je niewydajnymi w pewnych zastosowaniach. Dla niektórych zastosowań programy mogą być bardziej wydajne używając innych klas operujących na ciągach znaków niż klasa String.

StringBuider

Klasa StringBuilder reprezentuje zmienną klasę ciągu znaków. Przechowuje dane znaków w tablicy do której można dodawać, usuwać i zamieniać znaki bez potrzeby tworzenia nowego obiektu String i użycia intern pool
Zwykle programy używają klasy StringBuilder do budowania ciągu znaków wykonywanego w wielu krokach po czym używają metody klasy ToString która konwertuje ciąg znaków na typ String np:

string[] employeeNames =
{
"Able",
"Baker",
"Charley",
"Davis",
};
StringBuilder allNames = newStringBuilder();
foreach (string name in employeeNames)
{
allNames.Append("[" + name + "]" + Environment.NewLine);
}
employeeTextBox.Text = allNames.ToString();

Najbardziej użyteczne właściwości klasy StringBuilder:

  • Capacity - pobiera lub ustawia liczbę znaków, które mogą być przechowywane przez StringBuilder. Można użyć tej właściwości do alokacji pamięci potrzebnej dla operacji na ciągu zamiast pozwolić programowi na inkrementację pamięci kiedy jest to potrzebne.
  • Length - pobiera lub ustawia liczbę znaków przechowywane przez StringBuilder. Ustawienie mniejszej ilości niż rzeczywista prowadzi do obcięcia ciągu.

Najbardziej użyteczne metody klasy StringBuilder:

  • Append - dodaje ciąg znaków na koniec ciągu.
  • AppendFormat - formatuje listę obiektów i dodaje ich znakową reprezentacje na końcu ciągu.
  • EnsureCapacity - sprawdza czy obiekt ma wystarczającą pojemność.
  • Insert - wstawia ciąg znaków na wybraną pozycję ciągu.
  • Remove - usuwa zakres znaków z ciągu.
  • Replace - zamienia wszystkie wystąpienia znaku lub podciągu nowym znakiem lub podciągiem.
  • ToString - zwraca ciąg znaków skonwertowanych do typu String.

StringWriter

Klasa StringWriter w pewnych przypadkach pozwala na łatwiejsze budowanie ciągów znaków niż Stringbuilder. posiada metody które ułatwiają sekwencyjne zapisywanie wartości do ciągu znaków. 
Przydatne metody klasy StringWriter:

  • Flush - opróżnia wszystkie buforowane dane do obiektu StringWriter.
  • ToString - zwraca ciąg znaków skonwertowanych do typu String.
  • Write - dołącza obiekt do ciągu znaków.
  • WriteAsync - asynchronicznie dołącza znak, ciąg lub tablicę znaków na koniec ciągu.
  • WriteLine - dołącza obiekt do ciągu znaków po czym dodaje znak nowej linii. 
    Klasa StringWriter jest użyteczna kiedy wykonujemy tylko dołączanie wartości do ciągu znaków. Implementuje także interfejs TextWriter, więc może być użyteczna kiedy inna klasa używa TextWriter. Przykładowo metoda Serializeklasy XmlSerializer zwraca wynik w postaci TextWriter.

StringReader

Klasa StringReader jest implementacją interfejsu TextReader która czyta fragmenty danych pobranych z klasyStringbuilder. Posiada metody które ułatwiają sekwencyjne czytanie fragmentów tekstu ciągu znaków. 
Przydatne metody klasy StringReader:

  • Peek - zwraca następny znak z danych ale nie przechodzi do tego znaku.
  • Read - zwraca następny znak z danych i przechodzi do tego znaku. metoda przeciążona może czytać blok znaków.
  • ReadAsync - asynchronicznie czyta znaki do bufora.
  • ReadBlock - wczytuje maksymalną liczbę znaków do bufora.
  • ReadBlockAsync - asynchronicznie wczytuje maksymalną liczbę znaków do bufora.
  • ReadLine - wczytuje znaki do napotkania końca linii.
  • ReadLineAsync - asynchronicznie wczytuje znaki do napotkania końca linii.
  • ReadToEnd - zwraca pozostały tekst jako String.
  • ReadToEndAsync - asynchronicznie zwraca pozostały tekst jako String
    Klasa StringReader dostarcza dostępu do danych klasy StringBuilder na relatywnie niskim poziomie. Programy często wykorzystują tę klasę ponieważ inna metoda wymaga użycia tego typu.

Formatowanie wartości

Formatowanie wartości tak aby była poprawnie wyświetlana jest szczególnie ważnym typem konwersji. Typy danych takie jak DateTimedecimal lub double nie mogą być wyświetlone użytkownikowi bez wcześniejszej konwersji do czytelnego formatu. 
Dwie najczęściej używane metody formatowania wartości jako ciągu znaków to metody ToString i String.Format.

ToSting

Klasa Object wprowadza metodę ToString która jest dziedziczona przez każdą inną klasę. Domyślnie metoda ta zwraca nazwę typu obiektu jako ciąg znaków typu String ale większość klas nadpisuje tą metodę tak aby zwracała wartość obiektu jako ciąg znaków. 
Przykładowo metoda ToString dla zmiennej typu float o wartości 1.23 zwróci ciąg znaków równy “1.23”
Metoda ToString może posiadać także parametry format provider i formatting string. Ciąg formatujący pozwala na dostosowanie tekstu wynikowego. Przykładowo zmienna cost typu float i metoda cost.ToString(“0.00”) zwróci ciąg znaków z dwoma miejscami po przecinku.

String.Format

Metoda String.Format pozwala budować ciąg znaków zwierający wartości z wielu zmiennych formatowanych w różny sposób. 
Metoda String.Format posiada kilka przeciążonych wersji, ale najczęściej używana jest z ciągiem formatującym i listą argumentów użytych aby wypełnić ciąg formatujący. 
Każdy element w ciągu formatującym posiada następującą składnię:

{index[,length][:formatString]}

Index jest rozpoczynającym się od 0 numerem parametru który ma być użyty w ciągu wynikowym, length jest minimalną długością elementu a formatString jestformatem ciągu. jeżeli length jest wartością ujemną to wartość jest wyrównywana do lewej strony. Przykład użycia:

int i = 163;
Console.WriteLine(string.Format("{0} = {1,4} or 0x{2:X}", (char)i, i, i));

Ciąg formatujący “{0} = {1,4} or 0x{2:X}” ma trzy elementy:

  • {0} - wyświetla argument z indeksem 0 w domyślnym formacie.
  • {1,4} - wyświetla argument z indeksem 1 o szerokości co najmniej czterech znaków.
  • {2:x} - wyświetla argument z indeksem 2 w formacie X (wyświetla liczbę całkowitą jako szesnastkową). 
    Wynikiem działania jest: £ = 163 or 0xA3
    Argumenty nie muszą być użyte w formatowanym ciągu oraz mogą być używane w dowolnej kolejności lub wielokrotnie. poniższe wyrażenie jest poprawne:
stringtext = string.Format("{1} {4} {2} {1} {3}", "who", "I", "therefore", "am", "think");

Ciągi formatujące

Ciągi formatujące dzielą się na dwie kategorie:

  • Standardowe - pozwalają określić, jak chcesz wyświetlaną wartość na wysokim poziomie. Standardowe ciągi formatujące są zależne od lokalizacji. Przykładowo format daty “d” oznacza krótki wzorzec daty podobny do “3/14/2014” dla Stanów Zjednoczonych lub “14/03/2014” dla Francji.
  • Niestandardowe - pozwalają na budowę formatu którego brakuje na liście standardowych formatów. 
    Obie metody ToString i String.Format rozumieją setki standardowych i niestandardowych formatów. 
    Standardowe numeryczne ciągi formatujące:
  • C lub c - waluta (currency) np $12,345.67
  • D lub d - format dziesiętny (decimal - tylko typy całkowite) np 12345
  • E lub e - notacja naukowa np 1.234567E+004
  • F lub f - format zmiennoprzecinkowy np 12345.67
  • G lub g - format ogólny (naukowy lub zmiennoprzecinkowy) np 12345.67
  • N lub n - numer (number - z separatorami) np 12,345.67
  • P lub p - procent (pomnożony przez 100 i dodany znak “%” na końcu) np 12.00 %
  • X lub x - szesnastkowy (tylko typy całkowite) np 3039
    Niektóre z formatów mogą posiadać opcjonalnie precyzję oznaczającą liczbę znaków po przecinku. jeżeli zmiennavalue jest równa 12345.67 to value.ToString(“C4”) zwróci “$12,345.6700”.

Standardowe ciągi formatujące daty i czasu:

  • d - krótka data np “3/14/2014”
  • D - długa data np “Friday, March 14, 2012”
  • f - długa data z krótkim czasem np “Friday, March 14, 2012 2:15 PM”
  • F - długa data z długim czasem np “Friday, March 14, 2012 2:15:16 PM”
  • g - ogólna data z krótkim czasem np “3/14/2014 2:15 PM”
  • G - ogólna data z długim czasem np “3/14/2014 2:15:16 PM”
  • m lub M - Miesiąc/dzień np “March 14”
  • t - krótki czas np “2:15 PM”
  • T - długi czas np “2:15:16 PM”
  • y lub Y - Rok/miesiąc np “March, 2014” 
    Dodatkowo struktura DateTime posiada metody zwracające datę w formatach dDt i T. Są to metodyToShortDateStringToLongDateStringToShortTimeString, i ToLongTimeString.