Weźmy taki przykład dla typów wartościowych:
short[] x1 = new short[] { -34 };
ushort[] x2 = new ushort[] { 65000 };
//ushort[] x3 = (ushort[])(new short[] { -34 });
//short[] x4 = (short[])(new ushort[] { 34 });
//ushort[] x5 = (short[])x2;
//short[] x6 = (ushort[])x1;
//unchecked
//{
// ushort[] x5 = (short[])x2;
//}
//object[] x7 = (object[])x1;
object x8 = x1;
object x9 = x2;
ushort[] x10 = (ushort[])x8;
short[] x11 = (short[])x9;
//object[] x11a = (object[])x8;
//int[] x11b = (int[])x8;
bool x12 = x8 is short[];
bool x13 = x8 is ushort[];
bool x14 = x8 is int[];
bool x14a = x8.GetType().IsArray && x8.GetType().GetElementType() == typeof(short);
bool x14b = x8.GetType().IsArray && x8.GetType().GetElementType() == typeof(ushort);
int[] x16 = new int[] { -435 };
object x17 = x16;
bool x18 = x17 is int[];
bool x19 = x17 is uint[];
bool x20 = x17 is short[];
bool x21 = x17 is long[];
uint[] x22 = (uint[])x17;
Debug.WriteLine("x12: " + x12);
Debug.WriteLine("x13: " + x13);
Debug.WriteLine("x14: " + x14);
Debug.WriteLine("x14a: " + x14a);
Debug.WriteLine("x14b: " + x14b);
Debug.WriteLine("x18: " + x18);
Debug.WriteLine("x19: " + x19);
Debug.WriteLine("x20: " + x20);
Debug.WriteLine("x21: " + x21);
Debug.WriteLine("x10[0]: " + x10[0]);
Debug.WriteLine("x11[0]: " + x11[0]);
Debug.WriteLine("x22[0]: " + x22[0]);
Zakomentowane fragmenty albo się nie skompilują, albo zwrócą wyjątek. Wynik:
x12: True x13: True x14: False x14a: True x14b: False x18: True x19: True x20: False x21: False x10[0]: 65502 x11[0]: -536 x22[0]: 4294966861 Jak widzimy bezpośrednie rzutowanie jednego typu tablicy w inny typ tablicy nawet jeśli elementy tablicy są niejawnie konwertowalne nie jest możliwe. Kompilator nie pozwala na taką operację raportując ją jako błędną. Nawet jeśli spróbujemy użyć bloku
unchecked. W przypadku rzutowania typów wartościowych typu:
uint x = (uint)-3, kompilator także zwróci błąd. Tyle, że tutaj możemy go obejść za pomocą bloku
unchecked. Poza tym rzutowanie zmiennej typu
uint na zmienną typu
int jest możliwe w sposób niejawny:
uint x = 5; int y = (int)x. Kompilator daje nam do dyspozycji tego typu niejawne operatory konwersji. Ale już za pośrednictwem
object możemy rzutować tablice
int[] na
uint[]. Rzutowanie elementów tablicy odbywa się tak jak byśmy stosowali
(uint)int_value. Zauważmy, że np.
x1 i
x10 to referencjami do tego samego obiektu. W tym wypadku kompilator po prostu przestaje sprawdzać w trakcie kompilacji jak stosujemy dalej naszą zmienną typu
object, Zwróćmy uwagę na wartośc zmiennych
x12 i
x13. Operator
is nie rozróżnia pomiędzy
short[], a
ushort[]. Dla zmiennej
x14a i
x14b jest pokazane jak należy takie tablice rozróżniać, jeśli tego potrzebujemy. Rzutowanie np. pomiędzy
int[] i
short[] nie jest możliwe, gdyż rzutowanie nie tworzy nowej tablicy, co było by w tym wypadku potrzebne, ale tylko tworzy referencje innego typu wskazującego w to samo miejsce pamięci. Z tego samego powodu zawiedzie rzutowanie pomiędzy np.
int[], a
object[]. W przypadku tablic obiektów rozpatrzmy taki przykład:
class A
{
}
class B : A
{
}
{
B[] bb = new B[] { new B() };
A[] aa = (A[])bb;
B[] cc = (B[])aa;
//aa[0] = new A();
Debug.ReferenceEquals(aa, bb);
//Debug.Assert(bb.GetType().IsSubclassOf(aa.GetType()));
Debug.Assert(typeof(B).IsSubclassOf(typeof(A)));
}
{
A[] dd = new A[] { new B() };
//B[] ee = (B[])dd;
B[] ee = new B[1];
Array.Copy(dd, ee, 1);
}
{
A[] dd = new A[] { new A() };
//B[] ee = (B[])dd;
}
Widzimy, że rzutowanie tablicy elementów typu
B na tablicę elementów typu
A jest możliwa. Dla takiej tablicy jest możliwe także rzutowanie powrotne. Jednakże próba wstawienia do tablicy typu
A[] będącej zrzutowaną tablicą typu
B[] elementu typu
A zakończy się niepowodzeniem. Choć rzutowanie tablicy typu
B[] na tablicę typu
A[] jest możliwe to zauważmy, że tablica typu
B[] nie dziedziczy po tablicy typu
A[]. Rzutowanie w drugą stronę, tzn. z tablicy typu
A[] na tablicę typu
B[] nie jest możliwe. Oczywiście o ile nasza tablica
A[] nie jest tak naprawdę tablicą typu
B[]. Jednakże w tym momencie możemy się posłużyć do takiej operacji metodą
Array.Copy(), która indywidualnie dla każdego kopiowanego obiektu sprawdza jego kompatybilność. Jeszcze jeden przykład:
class D
{
public static implicit operator D(E a)
{
return new D();
}
}
class E
{
public static explicit operator E(D a)
{
return new E();
}
}
{
D[] dd = new D[] { new D() };
//E[] ee = (E[])dd;
E[] ee = new E[1];
//Array.Copy(dd, ee, 1);
}
{
D[] dd = new D[] { new D() };
object x = dd;
//E[] ee = (E[])x;
}
{
E[] ee = new E[] { new E() };
//D[] dd = (D[])ee;
D[] dd = new D[1];
//Array.Copy(ee, dd, 1);
}
{
E[] ee = new E[] { new E() };
object x = ee;
//D[] dd = (D[])x;
}
Widzimy, że istnienie jawnego, czy niejawnego operatora konwersji wcale nie umożliwia nam konwersji tablic takich elementów. Także metoda
Array.Copy() nie potrafi skorzystać z naszych operatorów konwersji. Ciekawa różnica pomiędzy tablicą typów wartościowych, a referencyjnych:
Debug.WriteLine(String.Format("{0}", new int[] { 1, 2, 3 }));
Debug.WriteLine(String.Format("{0}, {1}, {2}", new string[] { "a", "b", "c" }));
Tablica
int[] nie może zostać zrzutowana na tablice
object[], w przeciwieństwie do tablicy
string[]. Poprzednio zostało pokazane, że niemożliwa jest rzutowanie tablicy typów wartościowych na tablicę obiektów (i odwrotnie). Jak i niemożliwe jest rzutowanie pomiędzy tablicami różnych typów wartościowych. Czy możemy problemy tego rodzaju obejść za pomocą
Array.Copy() ?
{
int[] aa = new int[] { 1, 2, 3 };
object[] bb = new object[3];
Array.Copy(aa, bb, 3);
bb[0] = 567;
Array.Copy(bb, aa, 3);
long[] cc = new long[3];
Array.Copy(aa, cc, 3);
cc[0] = 456;
//Array.Copy(cc, aa, 3);
}
Widzimy, że konwersja pomiędzy różnymi typami wartościowymi jest niemożliwa. Ale za to pakowanie typów prostych i rozpakowywanie typów referencyjnych działa z tą metodą bez problemu.
Brak komentarzy:
Prześlij komentarz