2009-07-17

Porównywanie zawartości tablic w C#

Przykład:
{
    int[] a1 = new int[] { 145, 2, 3 };
    int[] a2 = new int[] { 145, 2, 3 };
    object[] a3 = new object[] { 145, 2, 3 };
    object[] a4 = new object[] { 145, 2, 3 };

    Debug.Assert((a1[0].Equals(a2[0])));
    Debug.Assert(a1[0] == a2[0]);

    Debug.Assert((a3[0].Equals(a4[0])));
    Debug.Assert(a3[0] != a4[0]);

    Debug.Assert((a1[0].Equals(a3[0])));
    //Debug.Assert(a1[0] == a3[0]);

    Debug.Assert((a3[0].Equals(a1[0])));
    //Debug.Assert(a3[0] == a1[0]);

    Debug.Assert(a1 != a2);
    Debug.Assert(!a1.Equals(a2));
    Debug.Assert(!Array.Equals(a1, a2));

    Debug.Assert(a3 != a4);
    Debug.Assert(!a3.Equals(a4));
    Debug.Assert(!Array.Equals(a3, a4));

    //Debug.Assert(a1 == a3);
    Debug.Assert(!a1.Equals(a3));
    Debug.Assert(!Array.Equals(a1, a3));

    //Debug.Assert(a3 == a1);
    Debug.Assert(!a3.Equals(a1));
    Debug.Assert(!Array.Equals(a3, a1));

    Debug.Assert(a1.SequenceEqual(a2));
    Debug.Assert(a3.SequenceEqual(a4));
    //Debug.Assert(a1.SequenceEqual(a3));
    //Debug.Assert(a3.SequenceEqual(a1));
}
Linie zakomentowane nie kompilują się. Jak widać obiekt typu Array, po którym dziedziczą wszystkie tablice, jak i te tablice nie przeładowują operatora ==, ani metody Equals(), dzięki temu wszystkie porównania tablic sprowadzają się do porównania referencji do nich. Sam obiekt Array nie udostępnia żadnej metody, która by nam w takim porównaniu pomogła. Więcej, samo środowisko czegoś takiego nam nie udostępnia, poza metodą SequenceEqual dostarczoną przez LINQ. Musimy więc sami zająć się sprawą porównywania tablic. Poniższy kod prezentuje 6 różnych metod porównywania tablic dla typów wartościowych i 5 dla typów referencyjnych. W metodzie Test1() porównywane są typy wartościowe. W metodzie Test2 porównywane są typy referencyjne. Dla każdej metody testowej najpierw sprawdzana jest poprawność algorytmów porównujących. A później wykonywane są dwie serie pomiarów. Najpierw duża tablica i mało sprawdzeń, później mała tablica i dużo sprawdzeń. Ilość porównań elementów tablicy jest taka sama w obu przypadkach. Chodzi tutaj o sprawdzenie kosztu samego porównania tablic.
public void Measure(int repeats, Action a)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();

    for (int i = 0; i < repeats; i++)
        a();

    sw.Stop();
    System.Console.WriteLine(sw.ElapsedMilliseconds);
}

public bool Compare1<T>(T[] ar1, T[] ar2)
{
    if (ar1.Length != ar2.Length)
        return false;

    for (int i = 0; i < ar1.Length; i++)
        if (!ar1[i].Equals(ar2[i]))
            return false;

    return true;
}

public bool Compare2<T>(T[] ar1, T[] ar2, Func<T, T, bool> comparer)
{
    if (ar1.Length != ar2.Length)
        return false;

    for (int i = 0; i < ar1.Length; i++)
        if (!comparer(ar1[i], ar2[i]))
            return false;

    return true;
}

public bool Compare3<T>(T[] ar1, T[] ar2, IEqualityComparer<T> comparer)
{
    if (ar1.Length != ar2.Length)
        return false;

    for (int i = 0; i < ar1.Length; i++)
        if (!comparer.Equals(ar1[i], ar2[i]))
            return false;

    return true;
}

public bool Compare4a(int[] ar1, int[] ar2)
{
    if (ar1.Length != ar2.Length)
        return false;

    for (int i = 0; i < ar1.Length; i++)
        if (ar1[i] != ar2[i])
            return false;

    return true;
}

public bool Compare4b(object[] ar1, object[] ar2)
{
    if (ar1.Length != ar2.Length)
        return false;

    for (int i = 0; i < ar1.Length; i++)
        if (!ar1[i].Equals(ar2[i]))
            return false;

    return true;
}

class Comparer1 : IEqualityComparer<int>
{
    public bool Equals(int x, int y)
    {
        return x == y;
    }

    public int GetHashCode(int obj)
    {
        throw new NotImplementedException();
    }
}

class Comparer2 : IEqualityComparer<object>
{
    public bool Equals(object x, object y)
    {
        return x.Equals(y);
    }

    public int GetHashCode(object obj)
    {
        throw new NotImplementedException();
    }
}

public void Test1()
{
    int[] ar1 = Enumerable.Range(1, 1000000).ToArray();
    int[] ar2 = Enumerable.Range(1, 1000000).ToArray();
    int[] ar3 = Enumerable.Range(2, 1000000).ToArray();
    int[] ar4 = Enumerable.Range(1, 1000000 - 1).ToArray();

    Debug.Assert(Compare1(ar1, ar2));
    Debug.Assert(Compare1(ar1, ar1));
    Debug.Assert(!Compare1(ar1, ar3));
    Debug.Assert(!Compare1(ar1, ar4));

    Debug.Assert(Compare2(ar1, ar2, (a, b) => (a == b)));
    Debug.Assert(Compare2(ar1, ar1, (a, b) => (a == b)));
    Debug.Assert(!Compare2(ar1, ar3, (a, b) => (a == b)));
    Debug.Assert(!Compare2(ar1, ar4, (a, b) => (a == b)));

    Debug.Assert(Compare2(ar1, ar2, (a, b) => (a.Equals(b))));
    Debug.Assert(Compare2(ar1, ar1, (a, b) => (a.Equals(b))));
    Debug.Assert(!Compare2(ar1, ar3, (a, b) => (a.Equals(b))));
    Debug.Assert(!Compare2(ar1, ar4, (a, b) => (a.Equals(b))));

    Debug.Assert(Compare3(ar1, ar2, new Comparer1()));
    Debug.Assert(Compare3(ar1, ar1, new Comparer1()));
    Debug.Assert(!Compare3(ar1, ar3, new Comparer1()));
    Debug.Assert(!Compare3(ar1, ar4, new Comparer1()));

    Debug.Assert(Compare4a(ar1, ar2));
    Debug.Assert(Compare4a(ar1, ar1));
    Debug.Assert(!Compare4a(ar1, ar3));
    Debug.Assert(!Compare4a(ar1, ar4));

    Measure(100, () => ar1.SequenceEqual(ar2));
    Measure(100, () => Compare1(ar1, ar2));
    Measure(100, () => Compare2(ar1, ar2, (a, b) => (a == b)));
    Measure(100, () => Compare2(ar1, ar2, (a, b) => (a.Equals(b))));
    Measure(100, () => Compare3(ar1, ar2, new Comparer1()));
    Measure(100, () => Compare4a(ar1, ar2));

    System.Console.WriteLine("----");

    ar1 = Enumerable.Range(1, 1000000 / 100000).ToArray();
    ar2 = Enumerable.Range(1, 1000000 / 100000).ToArray();

    Measure(100 * 100000, () => ar1.SequenceEqual(ar2));
    Measure(100 * 100000, () => Compare1(ar1, ar2));
    Measure(100 * 100000, () => Compare2(ar1, ar2, (a, b) => (a == b)));
    Measure(100 * 100000, () => Compare2(ar1, ar2, (a, b) => (a.Equals(b))));
    Measure(100 * 100000, () => Compare3(ar1, ar2, new Comparer1()));
    Measure(100 * 100000, () => Compare4a(ar1, ar2));

    System.Console.WriteLine("----");
}

void Test2()
{
    object[] ar1 = (from n in Enumerable.Range(1, 1000000) select (object)n).ToArray();
    object[] ar2 = (from n in Enumerable.Range(1, 1000000) select (object)n).ToArray();
    object[] ar3 = (from n in Enumerable.Range(2, 1000000) select (object)n).ToArray();
    object[] ar4 = (from n in Enumerable.Range(1, 1000000-1) select (object)n).ToArray();

    Debug.Assert(Compare1(ar1, ar2));
    Debug.Assert(Compare1(ar1, ar1));
    Debug.Assert(!Compare1(ar1, ar3));
    Debug.Assert(!Compare1(ar1, ar4));

    Debug.Assert(Compare2(ar1, ar2, (a, b) => (a.Equals(b))));
    Debug.Assert(Compare2(ar1, ar1, (a, b) => (a.Equals(b))));
    Debug.Assert(!Compare2(ar1, ar3, (a, b) => (a.Equals(b))));
    Debug.Assert(!Compare2(ar1, ar4, (a, b) => (a.Equals(b))));

    Debug.Assert(Compare3(ar1, ar2, new Comparer2()));
    Debug.Assert(Compare3(ar1, ar1, new Comparer2()));
    Debug.Assert(!Compare3(ar1, ar3, new Comparer2()));
    Debug.Assert(!Compare3(ar1, ar4, new Comparer2()));

    Debug.Assert(Compare4b(ar1, ar2));
    Debug.Assert(Compare4b(ar1, ar1));
    Debug.Assert(!Compare4b(ar1, ar3));
    Debug.Assert(!Compare4b(ar1, ar4));

    Measure(100, () => ar1.SequenceEqual(ar2));
    Measure(100, () => Compare1(ar1, ar2));
    Measure(100, () => Compare2(ar1, ar2, (a, b) => (a.Equals(b))));
    Measure(100, () => Compare3(ar1, ar2, new Comparer2()));
    Measure(100, () => Compare4b(ar1, ar2));

    System.Console.WriteLine("----");

    ar1 = (from n in Enumerable.Range(1, 1000000 / 100000) select (object)n).ToArray();
    ar2 = (from n in Enumerable.Range(1, 1000000 / 100000) select (object)n).ToArray();

    Measure(100 * 100000, () => ar1.SequenceEqual(ar2));
    Measure(100 * 100000, () => Compare1(ar1, ar2));
    Measure(100 * 100000, () => Compare2(ar1, ar2, (a, b) => (a.Equals(b))));
    Measure(100 * 100000, () => Compare3(ar1, ar2, new Comparer2()));
    Measure(100 * 100000, () => Compare4b(ar1, ar2));

    System.Console.WriteLine("----");
}

public void Test()
{
    Test1();
    Test2();
}
Wyniki: 2317 1891 1338 1578 1307 596 ---- 3708 1957 1531 1740 1552 730 ---- 3293 1193 2059 2300 1192 ---- 4972 1261 2151 2448 1246 ---- Najpierw może jeśli chodzi o sam koszt uruchomienia porównania tablic. Widać, że milion razy więcej wywołań przekłada się na niewielki wzrost miliona porównań. Czyli czas samego wywołania porównania jest mały. Czas ten wzrasta znacznie dla SequenceEqual(). Jeśli chodzi o czas samego porównania to jak można się było spodziewać najwolniejsza jest metoda SequenceEqual(). Dla typów wartościowych najszybsze jest porównanie bezpośrednie tablic. Wolniejsze jest wykorzystanie IEqualityComparer. Prawie tak samo szybkie jest użycie jako metody porównawczej wyrażenia lambda z bezpośrednim porównaniem wartości. Dalej jest porównanie z użyciem wyrażenia lambda, ale wykorzystujące metodę Equals(). Dalej metoda Compare1(), która także wykorzystuje Equals() do porównania. Najwolniejsza jest wspomniana SequenceEqual(). Dla typów referencyjnych jest trochę inaczej. Nie ma tutaj sensu osobno porównywać za pomocą operatora == i Equals() stąd sposobów porównania jest o jeden mniej. Najszybciej działa bezpośrednie porównanie tablic. Dalej jest metoda Compare1 wykorzystująca Equals(). Dalej jest wykorzystanie wyrażenia lambda, które wykorzystuje Equals(). Dalej wykorzystanie IEqualityComparer. Na końcu wykorzystanie SequenceEqual(). Jeśli chodzi o metody, które nadają się zarówno do porównania typów wartościowych, jak i referencyjnych to w grę wchodzi tylko wykorzystanie SequenceEqual() lub Compare1. Compare1 jest szybsze, ale za to SequenceEqual() bardziej uniwersalne (działa na enumeratorach) i dostępne w standardzie. Jeśli chodzi o prędkość to niestety czym coś jest bardziej uniwersalne to tym jest wolniejsze. Skupmy się na metodzie Compare1(). Napiszmy ją tak by stała się uniwersalną metodą rozszerzoną:
public static class ArrayExtensions
{
    public static bool SameArrays<T>(this T[] ar1, T[] ar2)
    {
        if (Object.ReferenceEquals(ar1, ar2))
            return true;

        if (ar1.Length != ar2.Length)
            return false;

        for (int i = 0; i < ar1.Length; i++)
            if (!ar1[i].Equals(ar2[i]))
                return false;

        return true;
    }
}

public void Test3()
{
    {
        int[] ar1 = Enumerable.Range(1, 1000000).ToArray();
        int[] ar2 = Enumerable.Range(1, 1000000).ToArray();
        int[] ar3 = Enumerable.Range(2, 1000000).ToArray();
        int[] ar4 = Enumerable.Range(1, 1000000 - 1).ToArray();

        Debug.Assert(ar1.SameArrays(ar2));
        Debug.Assert(ar1.SameArrays(ar1));
        Debug.Assert(!ar1.SameArrays(ar3));
        Debug.Assert(!ar1.SameArrays(ar4));
    }

    {
        object[] ar1 = (from n in Enumerable.Range(1, 1000000) select (object)n).ToArray();
        object[] ar2 = (from n in Enumerable.Range(1, 1000000) select (object)n).ToArray();
        object[] ar3 = (from n in Enumerable.Range(2, 1000000) select (object)n).ToArray();
        object[] ar4 = (from n in Enumerable.Range(1, 1000000 - 1) select (object)n).ToArray();

        Debug.Assert(ar1.SameArrays(ar2));
        Debug.Assert(ar1.SameArrays(ar1));
        Debug.Assert(!ar1.SameArrays(ar3));
        Debug.Assert(!ar1.SameArrays(ar4));
    }
}
Dodatkowo, dodałem tam drobną optymalizację, by nie porównywać tych samych obiektów.

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.

Konwersje typów prostych ze znakiem na typy bez znaku

Przykładowy kod:
{
    short x1 = 4;
    short x2 = -4;

    ushort x3 = (ushort)x1;
    ushort x4 = (ushort)x2;

    uint x5 = (uint)x1;
    uint x6 = (uint)x2;

    uint x7 = (uint)(ushort)x1;
    uint x8 = (uint)(ushort)x2;

    Debug.WriteLine(x3);
    Debug.WriteLine(x4);
    Debug.WriteLine(x5);
    Debug.WriteLine(x6);
    Debug.WriteLine(x7);
    Debug.WriteLine(x8);

}

{
    int x1 = 4;
    int x2 = -4;

    uint x3 = (uint)x1;
    uint x4 = (uint)x2;

    ulong x5 = (ulong)x1;
    ulong x6 = (ulong)x2;

    ulong x7 = (ulong)(ushort)x1;
    ulong x8 = (ulong)(ushort)x2;

    ulong x9 = (ulong)(uint)x1;
    ulong x10 = (ulong)(uint)x2;

    Debug.WriteLine(x3);
    Debug.WriteLine(x4);
    Debug.WriteLine(x5);
    Debug.WriteLine(x6);
    Debug.WriteLine(x7);
    Debug.WriteLine(x8);
    Debug.WriteLine(x9);
    Debug.WriteLine(x10);
}
Wynik: 4 65532 4 4294967292 4 65532 4 4294967292 4 18446744073709551612 4 65532 4 4294967292 W skrócie po analizie możemy powiedzieć, że dla typów ze znakiem podczas ich konwersji na typ bez znaku o większym zakresie, jeśli liczba jest ujemna, nowe najstarsze bity uzupełniane są jedynkami. Jeśli liczba jest ujemna uzupełnienie następuje zerami.

LINQ - metoda Cast()

Metoda Cast<T>() służy do konwersji obiektu typu IEnumerable<A> na IEnumerable<T>. Przykład zastosowania:
int[] x11 = new int[] { 1, 2, 3, 4, 4, 3 };
object[] x21 = x11.Cast<object>().ToArray();
int[] x31 = x21.Cast<int>().ToArray();
Cast<T>() zwraca jako wynik obiekt typu IEnumerable<T>, stąd w powyższym przykładzie potrzebne jest jeszcze wywołanie ToArray(). Ale takie coś nie zadziała:
int[] x11 = new int[] { 1, 2, 3, 4, 4, 3 };
uint[] x21 = x11.Cast<uint>().ToArray();
int[] x31 = x21.Cast<int>().ToArray();
Problem polega na tym, że Cast<T>() wykonuje konwersje tylko jeśli wszystkie obiekty kolekcji źródłowej są także obiektami typu T. Jeśli warunek nie jest spełniony dostaniemy wyjątek. Nie nastąpi to w samej metodzie Cast<T>(), dopiero próba enumeracji po zwróconym enumeratorze zakończy się wyjątkiem (efekt opóźnionego wykonywania kodu). Cast<T>() nie uwzględnia także bezpośrednich operatorów konwersji.