Poniżej znajduje się post z następującego artykułu :
Różnica między przymusem a rzucaniem jest często pomijana. Rozumiem dlaczego; wiele języków ma tę samą (lub podobną) składnię i terminologię dla obu operacji. Niektóre języki mogą nawet odnosić się do dowolnej konwersji jako „rzutowanie”, ale poniższe wyjaśnienie odnosi się do pojęć w CTS.
Jeśli próbujesz przypisać wartość pewnego typu do lokalizacji innego typu, możesz wygenerować wartość nowego typu, która ma podobne znaczenie do oryginału. To jest przymus. Wymuszenie pozwala na użycie nowego typu poprzez utworzenie nowej wartości, która w pewien sposób przypomina oryginał. Niektóre wymuszenia mogą odrzucać dane (np. Konwertowanie int 0x12345678 na krótkie 0x5678), podczas gdy inne nie (np. Konwertowanie int 0x00000008 na krótkie 0x0008 lub długie 0x0000000000000008).
Przypomnij sobie, że wartości mogą mieć wiele typów. Jeśli Twoja sytuacja jest nieco inna i chcesz wybrać tylko inny typ wartości, narzędziem do tego zadania jest rzutowanie. Rzutowanie po prostu wskazuje, że chcesz operować na określonym typie, który zawiera wartość.
Różnica na poziomie kodu różni się od C # do IL. W C # zarówno rzutowanie, jak i przymus wyglądają dość podobnie:
static void ChangeTypes(int number, System.IO.Stream stream)
{
long longNumber = number;
short shortNumber = (short)number;
IDisposable disposableStream = stream;
System.IO.FileStream fileStream = (System.IO.FileStream)stream;
}
Na poziomie IL są zupełnie inne:
ldarg.0
conv.i8
stloc.0
ldarg.0
conv.i2
stloc.1
ldarg.1
stloc.2
ldarg.1
castclass [mscorlib]System.IO.FileStream
stloc.3
Jeśli chodzi o poziom logiczny, istnieje kilka ważnych różnic. Najważniejsze do zapamiętania jest to, że przymus tworzy nową wartość, podczas gdy rzucanie nie. Tożsamość wartości pierwotnej i wartości po rzutowaniu są takie same, podczas gdy tożsamość wartości wymuszonej różni się od wartości pierwotnej; koersion tworzy nową, odrębną instancję, podczas gdy rzutowanie nie. Konsekwencją jest to, że wynik rzutowania i oryginał zawsze będą równoważne (zarówno pod względem tożsamości, jak i równości), ale wymuszona wartość może być równa oryginałowi lub nie, i nigdy nie ma takiej samej tożsamości.
W powyższych przykładach łatwo jest zobaczyć konsekwencje przymusu, ponieważ typy liczbowe są zawsze kopiowane według wartości. Podczas pracy z typami referencyjnymi sprawy stają się nieco trudniejsze.
class Name : Tuple<string, string>
{
public Name(string first, string last)
: base(first, last)
{
}
public static implicit operator string[](Name name)
{
return new string[] { name.Item1, name.Item2 };
}
}
W poniższym przykładzie jedna konwersja to rzut, a druga to przymus.
Tuple<string, string> tuple = name;
string[] strings = name;
Po tych konwersjach krotka i nazwa są równe, ale ciągi znaków nie są równe żadnemu z nich. Możesz nieco poprawić sytuację (lub nieco bardziej zagmatwać), implementując Equals () i operator == () w klasie Name w celu porównania nazwy i ciągu []. Te operatory „rozwiązałyby” problem z porównaniem, ale nadal istniałyby dwie oddzielne instancje; jakakolwiek modyfikacja łańcuchów nie byłaby odzwierciedlona w nazwie lub krotce, podczas gdy zmiany w nazwie lub krotce byłyby odzwierciedlone w nazwie i krotce, ale nie w łańcuchach.
Chociaż powyższy przykład miał na celu zilustrowanie pewnych różnic między rzutowaniem a wymuszeniem, służy on również jako doskonały przykład tego, dlaczego należy zachować szczególną ostrożność podczas używania operatorów konwersji z typami referencyjnymi w języku C #.