2009-07-17

Rzutowanie tablic w C#

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