C # „to” wydajność operatora


102

Mam program, który wymaga szybkiego działania. W ramach jednej z wewnętrznych pętli muszę przetestować typ obiektu, aby zobaczyć, czy dziedziczy on z określonego interfejsu.

Jednym ze sposobów byłoby użycie wbudowanej funkcji sprawdzania typu środowiska CLR. Najbardziej elegancka metoda, która prawdopodobnie zawiera słowo kluczowe „jest”:

if (obj is ISpecialType)

Innym podejściem byłoby nadanie klasie bazowej mojej własnej wirtualnej funkcji GetType (), która zwraca wstępnie zdefiniowaną wartość wyliczenia (w moim przypadku potrzebuję tylko wartości bool). Ta metoda byłaby szybka, ale mniej elegancka.

Słyszałem, że istnieje instrukcja IL przeznaczona specjalnie dla słowa kluczowego „is”, ale nie oznacza to, że jest ona wykonywana szybko po przetłumaczeniu na natywny asembler. Czy ktoś może podzielić się spostrzeżeniami na temat wykonania metody „jest” w porównaniu z inną metodą?

AKTUALIZACJA: Dzięki za wszystkie odpowiedzi! Wydaje się, że kilka pomocnych punktów jest rozłożonych na odpowiedzi: uwaga Andrew dotycząca automatycznego wykonywania rzutów jest niezbędna, ale dane dotyczące wydajności zebrane przez Binary Worrier i Iana są również niezwykle przydatne. Byłoby wspaniale, gdyby jedna z odpowiedzi została zredagowana tak, aby zawierała wszystkie te informacje.


2
btw, CLR nie da ci możliwości stworzenia własnej funkcji Type GetType (), ponieważ łamie jedną z głównych reguł CLR - prawdziwie typy
abatishchev

1
Eee, nie jestem do końca pewien, co masz na myśli, mówiąc o zasadzie „prawdziwie typów”, ale rozumiem, że CLR ma wbudowaną funkcję Type GetType (). Gdybym użył tej metody, byłaby to funkcja o innej nazwie zwracająca jakieś wyliczenie, więc nie byłoby konfliktu nazwa / symbol.
JubJub

3
Myślę, że abatishchev oznaczał „bezpieczeństwo typów”. Funkcja GetType () nie jest wirtualna, aby zapobiec kłamstwu typu na temat samego siebie, a tym samym zachować bezpieczeństwo typów.
Andrew Hare

2
Czy rozważałeś wstępne pobieranie i buforowanie zgodności typu, aby nie trzeba było tego robić w pętlach? Wydaje się, że każde pytanie dotyczące perf jest zawsze dające +1, ale wydaje mi się to po prostu słabym zrozumieniem języka c #. Czy rzeczywiście jest za wolno? W jaki sposób? Czego próbowałeś? Oczywiście niewiele biorąc pod uwagę twoje komentarze na temat odpowiedzi ...
Gusdor

Odpowiedzi:


114

Używanie ismoże zaszkodzić wydajności, jeśli po sprawdzeniu typu rzutujesz na ten typ. isw rzeczywistości rzutuje obiekt na sprawdzany typ, więc każde kolejne rzutowanie jest zbędne.

Jeśli mimo wszystko zamierzasz przesyłać, oto lepsze podejście:

ISpecialType t = obj as ISpecialType;

if (t != null)
{
    // use t here
}

1
Dzięki. Ale jeśli nie zamierzam rzutować obiektu, jeśli warunek się nie powiedzie, czy lepiej byłoby użyć funkcji wirtualnej do testowania typu?
JubJub

4
@JubJub: nie. Niepowodzenie aszasadniczo wykonuje tę samą operację co is(a mianowicie sprawdzenie typu). Jedyną różnicą jest to, że nullzamiast tego wraca false.
Konrad Rudolph

74

Jestem z Ianem , prawdopodobnie nie chcesz tego robić.

Jednak, żebyś wiedział, różnica między tymi dwoma jest bardzo mała, ponad 10 000 000 iteracji

  • Sprawdzenie wyliczenia przychodzi po 700 milisekundach (w przybliżeniu)
  • Kontrola IS przychodzi po 1000 milisekundach (w przybliżeniu)

Osobiście nie rozwiązałbym tego problemu w ten sposób, ale gdybym był zmuszony wybrać jedną metodę, byłaby to wbudowana kontrola IS, różnica w wydajności nie jest warta rozważenia narzutu kodowania.

Moje klasy bazowe i pochodne

class MyBaseClass
{
    public enum ClassTypeEnum { A, B }
    public ClassTypeEnum ClassType { get; protected set; }
}

class MyClassA : MyBaseClass
{
    public MyClassA()
    {
        ClassType = MyBaseClass.ClassTypeEnum.A;
    }
}
class MyClassB : MyBaseClass
{
    public MyClassB()
    {
        ClassType = MyBaseClass.ClassTypeEnum.B;
    }
}

JubJub: Zgodnie z prośbą, więcej informacji na temat testów.

Uruchomiłem oba testy z aplikacji konsoli (kompilacja do debugowania), a każdy test wygląda następująco

static void IsTest()
{
    DateTime start = DateTime.Now;
    for (int i = 0; i < 10000000; i++)
    {
        MyBaseClass a;
        if (i % 2 == 0)
            a = new MyClassA();
        else
            a = new MyClassB();
        bool b = a is MyClassB;
    }
    DateTime end = DateTime.Now;
    Console.WriteLine("Is test {0} miliseconds", (end - start).TotalMilliseconds);
}

Działając w zwolnionym tempie, dostaję różnicę 60 - 70 ms, jak Ian.

Dalsza aktualizacja - 25 października 2012 r.
Po kilku latach nieobecności zauważyłem coś na ten temat, kompilator może zdecydować się na pominięcie bool b = a is MyClassBw wydaniu, ponieważ b nie jest nigdzie używany.

Ten kod. . .

public static void IsTest()
{
    long total = 0;
    var a = new MyClassA();
    var b = new MyClassB();
    var sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < 10000000; i++)
    {
        MyBaseClass baseRef;
        if (i % 2 == 0)
            baseRef = a;//new MyClassA();
        else
            baseRef = b;// new MyClassB();
        //bool bo = baseRef is MyClassB;
        bool bo = baseRef.ClassType == MyBaseClass.ClassTypeEnum.B;
        if (bo) total += 1;
    }
    sw.Stop();
    Console.WriteLine("Is test {0} miliseconds {1}", sw.ElapsedMilliseconds, total);
}

. . . konsekwentnie pokazuje, że isczek nadchodzi po około 57 milisekundach, a porównanie wyliczeniowe po 29 milisekundach.

NB nadal wolałbym isczek, różnica jest zbyt mała, żeby się nią przejmować


35
+1 za faktyczne testowanie wydajności, zamiast zakładać.
Jon Tackabury

3
Znacznie lepiej jest zrobić test z klasą Stopwatch zamiast DateTime.
Teraz,

2
Biorę to pod uwagę, ale w tym przypadku nie sądzę, żeby miało to wpływ na wynik. Dzięki :)
Binary Worrier

11
@Binary Worrier - Twoje nowe alokacje operatorów dla klas całkowicie przyćmią wszelkie różnice w wydajności operacji „jest”. Dlaczego nie usuniesz tych nowych operacji, ponownie wykorzystując dwa różne wstępnie przydzielone wystąpienia, a następnie ponownie uruchom kod i opublikuj wyniki.

1
@mcmillab: Gwarantuję, że cokolwiek robisz, będziesz mieć wąskie gardła o wiele rzędów wielkości większe niż jakakolwiek degradacja wydajności, którą powoduje isoperator, i że głośno o projektowaniu i kodowaniu wokół isoperatora będzie kosztować fortunę jakości kodu i ostatecznie będzie również samoobronny pod względem wydajności. W tym przypadku podtrzymuję moje oświadczenie. „Is” operator nie będzie problem z wykonania wykonawczego.
Binary Worrier

23

Ok, więc rozmawiałem z kimś o tym i postanowiłem przetestować to więcej. O ile wiem, wydajność asi isoba są bardzo dobre w porównaniu z testowaniem własnego elementu członkowskiego lub funkcji do przechowywania informacji o typie.

Skorzystałem Stopwatch, czego właśnie się nauczyłem, może nie jest najbardziej niezawodnym podejściem, więc też spróbowałem UtcNow. Później wypróbowałem także podejście oparte na czasie procesora, które wydaje się podobne do UtcNowuwzględniania nieprzewidywalnych czasów tworzenia. Próbowałem też uczynić klasę bazową nieabstrakcyjną bez wirtuali, ale nie przyniosło to znaczącego efektu.

Uruchomiłem to na Quad Q6600 z 16 GB pamięci RAM. Nawet przy iteracjach 50 mil, liczby nadal odbijają się w okolicach +/- 50 milisekund, więc nie czytałbym zbyt wiele z drobnych różnic.

Ciekawie było zobaczyć, że x64 tworzył się szybciej, ale wykonywany jako / jest wolniejszy niż x86

x64 Tryb wydania:
Stoper:
As: 561 ms
Jest: 597 ms
Właściwość podstawowa: 539 ms
Pole podstawowe: 555 ms Pole
podstawowe RO: 552 ms
Wirtualny test GetEnumType (): 556 ms
Wirtualny test IsB (): 588 ms
Czas tworzenia: 10416 ms

UtcNow:
As: 499ms
Is: 532ms
Właściwość podstawowa: 479ms
Pole bazowe: 502ms Pole
bazowe RO: 491ms
Wirtualne GetEnumType (): 502ms
Wirtualne bool IsB (): 522ms
Czas tworzenia: 285ms (Ta liczba wydaje się niewiarygodna z UtcNow. Uzyskuję również 109ms i 806 ms.)

x86 Tryb wydania:
Stoper:
As: 391 ms
Jest: 423 ms
Właściwość podstawowa: 369 ms
Pole podstawowe: 321 ms Pole
podstawowe RO: 339 ms
Wirtualny test GetEnumType (): 361 ms
Wirtualny test IsB (): 365 ms
Czas tworzenia: 14106 ms

UtcNow:
As: 348ms
Is: 375ms
Właściwość podstawowa: 329ms
Pole podstawowe: 286ms Pole
bazowe RO: 309ms
Virtual GetEnumType (): 321ms
Virtual bool IsB (): 332ms
Czas tworzenia: 544ms (ta liczba wydaje się niewiarygodna z UtcNow.)

Oto większość kodu:

    static readonly int iterations = 50000000;
    void IsTest()
    {
        Process.GetCurrentProcess().ProcessorAffinity = (IntPtr)1;
        MyBaseClass[] bases = new MyBaseClass[iterations];
        bool[] results1 = new bool[iterations];

        Stopwatch createTime = new Stopwatch();
        createTime.Start();
        DateTime createStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            if (i % 2 == 0) bases[i] = new MyClassA();
            else bases[i] = new MyClassB();
        }
        DateTime createStop = DateTime.UtcNow;
        createTime.Stop();


        Stopwatch isTimer = new Stopwatch();
        isTimer.Start();
        DateTime isStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] =  bases[i] is MyClassB;
        }
        DateTime isStop = DateTime.UtcNow; 
        isTimer.Stop();
        CheckResults(ref  results1);

        Stopwatch asTimer = new Stopwatch();
        asTimer.Start();
        DateTime asStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i] as MyClassB != null;
        }
        DateTime asStop = DateTime.UtcNow; 
        asTimer.Stop();
        CheckResults(ref  results1);

        Stopwatch baseMemberTime = new Stopwatch();
        baseMemberTime.Start();
        DateTime baseStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].ClassType == MyBaseClass.ClassTypeEnum.B;
        }
        DateTime baseStop = DateTime.UtcNow;
        baseMemberTime.Stop();
        CheckResults(ref  results1);

        Stopwatch baseFieldTime = new Stopwatch();
        baseFieldTime.Start();
        DateTime baseFieldStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].ClassTypeField == MyBaseClass.ClassTypeEnum.B;
        }
        DateTime baseFieldStop = DateTime.UtcNow;
        baseFieldTime.Stop();
        CheckResults(ref  results1);


        Stopwatch baseROFieldTime = new Stopwatch();
        baseROFieldTime.Start();
        DateTime baseROFieldStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].ClassTypeField == MyBaseClass.ClassTypeEnum.B;
        }
        DateTime baseROFieldStop = DateTime.UtcNow;
        baseROFieldTime.Stop();
        CheckResults(ref  results1);

        Stopwatch virtMethTime = new Stopwatch();
        virtMethTime.Start();
        DateTime virtStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].GetClassType() == MyBaseClass.ClassTypeEnum.B;
        }
        DateTime virtStop = DateTime.UtcNow;
        virtMethTime.Stop();
        CheckResults(ref  results1);

        Stopwatch virtMethBoolTime = new Stopwatch();
        virtMethBoolTime.Start();
        DateTime virtBoolStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].IsB();
        }
        DateTime virtBoolStop = DateTime.UtcNow;
        virtMethBoolTime.Stop();
        CheckResults(ref  results1);


        asdf.Text +=
        "Stopwatch: " + Environment.NewLine 
          +   "As:  " + asTimer.ElapsedMilliseconds + "ms" + Environment.NewLine
           +"Is:  " + isTimer.ElapsedMilliseconds + "ms" + Environment.NewLine
           + "Base property:  " + baseMemberTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Base field:  " + baseFieldTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Base RO field:  " + baseROFieldTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Virtual GetEnumType() test:  " + virtMethTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Virtual IsB() test:  " + virtMethBoolTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Create Time :  " + createTime.ElapsedMilliseconds + "ms" + Environment.NewLine + Environment.NewLine+"UtcNow: " + Environment.NewLine + "As:  " + (asStop - asStart).Milliseconds + "ms" + Environment.NewLine + "Is:  " + (isStop - isStart).Milliseconds + "ms" + Environment.NewLine + "Base property:  " + (baseStop - baseStart).Milliseconds + "ms" + Environment.NewLine + "Base field:  " + (baseFieldStop - baseFieldStart).Milliseconds + "ms" + Environment.NewLine + "Base RO field:  " + (baseROFieldStop - baseROFieldStart).Milliseconds + "ms" + Environment.NewLine + "Virtual GetEnumType():  " + (virtStop - virtStart).Milliseconds + "ms" + Environment.NewLine + "Virtual bool IsB():  " + (virtBoolStop - virtBoolStart).Milliseconds + "ms" + Environment.NewLine + "Create Time :  " + (createStop-createStart).Milliseconds + "ms" + Environment.NewLine;
    }
}

abstract class MyBaseClass
{
    public enum ClassTypeEnum { A, B }
    public ClassTypeEnum ClassType { get; protected set; }
    public ClassTypeEnum ClassTypeField;
    public readonly ClassTypeEnum ClassTypeReadonlyField;
    public abstract ClassTypeEnum GetClassType();
    public abstract bool IsB();
    protected MyBaseClass(ClassTypeEnum kind)
    {
        ClassTypeReadonlyField = kind;
    }
}

class MyClassA : MyBaseClass
{
    public override bool IsB() { return false; }
    public override ClassTypeEnum GetClassType() { return ClassTypeEnum.A; }
    public MyClassA() : base(MyBaseClass.ClassTypeEnum.A)
    {
        ClassType = MyBaseClass.ClassTypeEnum.A;
        ClassTypeField = MyBaseClass.ClassTypeEnum.A;            
    }
}
class MyClassB : MyBaseClass
{
    public override bool IsB() { return true; }
    public override ClassTypeEnum GetClassType() { return ClassTypeEnum.B; }
    public MyClassB() : base(MyBaseClass.ClassTypeEnum.B)
    {
        ClassType = MyBaseClass.ClassTypeEnum.B;
        ClassTypeField = MyBaseClass.ClassTypeEnum.B;
    }
}

45
(Jakiś bonus 5 szekspirowskich inspirowanych ...) Być albo nie być: oto jest pytanie: czy jest szlachetniejszy w kodzie, by cierpieć Wyliczenia i właściwości abstrakcyjnych baz, czy też skorzystać z ofert pośrednika lingwista I wierząc im, odwołując się do jego instrukcji? Zgadnąć: zastanawiać się; Już nie; a czas, aby to rozpoznać, zakończymy ból głowy i tysiące podświadomych zdziwień, których spadkobiercami są programiści. To zamknięcie, którego należy pragnąć. Nie umrzeć, ale spać; Tak, będę spał, być może marzenie o tym jest i jak o tym, co można wyprowadzić z najbardziej podstawowej klasy.
Jared Thirsk

Czy możemy z tego wywnioskować, że dostęp do właściwości jest szybszy na x64 niż dostęp do pola !!! Bo to dla mnie cholerna niespodzianka, jak to może być?
Didier A.

1
Nie podsumowałbym tego, ponieważ: „Nawet przy iteracjach 50 mil, liczby nadal odbijają się w okolicach +/- 50 milisekund, więc nie czytałbym zbyt wiele z drobnych różnic”.
Jared Thirsk

16

Andrew ma rację. W rzeczywistości w przypadku analizy kodu program Visual Studio zgłasza to jako niepotrzebne rzutowanie.

Jeden pomysł (nie wiedząc, co robisz, to trochę strzał w ciemno), ale zawsze radzono mi unikać sprawdzania w ten sposób i zamiast tego mieć inne zajęcia. Zamiast więc sprawdzać i wykonywać różne czynności w zależności od typu, spraw, aby klasa wiedziała, jak sama przetworzyć ...

np. Obj może być ISpecialType lub IType;

oba mają zdefiniowaną metodę DoStuff (). Dla IType może po prostu zwrócić lub wykonać niestandardowe rzeczy, podczas gdy ISpecialType może zrobić inne rzeczy.

To następnie całkowicie usuwa wszelkie rzutowanie, sprawia, że ​​kod jest czystszy i łatwiejszy w utrzymaniu, a klasa wie, jak wykonywać własne zadania.


Tak, ponieważ wszystko, co zrobię, jeśli testy typu true, to wywołanie określonej metody interfejsu, mogę po prostu przenieść tę metodę interfejsu do klasy bazowej i domyślnie nic nie robić. To może być bardziej eleganckie niż tworzenie funkcji wirtualnej do testowania typu.
JubJub

Zrobiłem podobny test jak Binary Worrier po komentarzach abatishcheva i znalazłem tylko 60 ms różnicę ponad 10000000 itteracji.
Ian

1
Wow, dzięki za pomoc. Przypuszczam, że na razie pozostanę przy użyciu operatorów sprawdzających typ, chyba że reorganizacja struktury klas wydaje się odpowiednia. Użyję operatora „as”, jak zasugerował Andrew, ponieważ nie chcę rzutować nadmiarowo.
JubJub

15

Dokonałem porównania wydajności dla dwóch możliwości porównania typów

  1. myobject.GetType () == typeof (MyClass)
  2. myobject to MyClass

Rezultat jest następujący: użycie „is” jest około 10x szybsze !!!

Wynik:

Czas na porównanie typów: 00: 00: 00.456

Czas na porównanie: 00: 00: 00.042

Mój kod:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace ConsoleApplication3
{
    class MyClass
    {
        double foo = 1.23;
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyClass myobj = new MyClass();
            int n = 10000000;

            Stopwatch sw = Stopwatch.StartNew();

            for (int i = 0; i < n; i++)
            {
                bool b = myobj.GetType() == typeof(MyClass);
            }

            sw.Stop();
            Console.WriteLine("Time for Type-Comparison: " + GetElapsedString(sw));

            sw = Stopwatch.StartNew();

            for (int i = 0; i < n; i++)
            {
                bool b = myobj is MyClass;
            }

            sw.Stop();
            Console.WriteLine("Time for Is-Comparison: " + GetElapsedString(sw));
        }

        public static string GetElapsedString(Stopwatch sw)
        {
            TimeSpan ts = sw.Elapsed;
            return String.Format("{0:00}:{1:00}:{2:00}.{3:000}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds);
        }
    }
}

13

Andrew Hare powiedział o utracie wydajności podczas issprawdzania, a następnie rzucania było prawidłowe, ale w C # 7.0 możemy to zrobić, to sprawdzić dopasowanie wzorca czarownicy, aby uniknąć późniejszego rzucania:

if (obj is ISpecialType st)
{
   //st is in scope here and can be used
}

Co więcej, jeśli musisz sprawdzić między wieloma typami C # 7.0 konstrukcje dopasowywania wzorców teraz umożliwiają to switchna typach:

public static double ComputeAreaModernSwitch(object shape)
{
    switch (shape)
    {
        case Square s:
            return s.Side * s.Side;
        case Circle c:
            return c.Radius * c.Radius * Math.PI;
        case Rectangle r:
            return r.Height * r.Length;
        default:
            throw new ArgumentException(
                message: "shape is not a recognized shape",
                paramName: nameof(shape));
    }
}

Możesz przeczytać więcej o dopasowywaniu wzorców w C # w dokumentacji tutaj .


1
To na pewno poprawne rozwiązanie, ale ta funkcja dopasowywania wzorców C # mnie zasmuca, gdy zachęca do kodowania „zazdrości o funkcje” w ten sposób. Z pewnością powinniśmy dążyć do hermetyzacji logiki, w której tylko wyprowadzone obiekty „wiedzą”, jak obliczyć swój własny obszar, a potem po prostu zwracają wartość?
Dib

2
SO potrzebuje przycisków filtru (na pytaniu) dla odpowiedzi, które dotyczą nowszych wersji frameworka, platformy itp. Ta odpowiedź stanowi podstawę poprawnej dla C # 7.
Nick Westgate

1
Ideały @Dib OOP są wyrzucane z okna, gdy pracujesz z typami / klasami / interfejsami, których nie kontrolujesz. To podejście jest również przydatne podczas obsługi wyniku funkcji, która może zwracać jedną z wielu wartości zupełnie różnych typów (ponieważ C # nadal nie obsługuje jeszcze typów unii - można używać bibliotek takich jak, OneOf<T...>ale mają one poważne wady) .
Dai

4

Na wypadek gdyby ktoś się zastanawiał, testy wykonałem w silniku Unity 2017.1 ze skryptami w wersji runtime .NET4.6 (Experimantal) na notebooku z procesorem i5-4200U. Wyniki:

Average Relative To Local Call LocalCall 117.33 1.00 is 241.67 2.06 Enum 139.33 1.19 VCall 294.33 2.51 GetType 276.00 2.35

Cały artykuł: http://www.ennoble-studios.com/tuts/unity-c-performance-comparison-is-vs-enum-vs-virtual-call.html


Link do artykułu jest martwy.
James Wilkins

Odnowienie linku @James.
Gru

Dobra rzecz - ale nie przegłosowałem cię (właściwie i tak zagłosowałem za); Na wypadek, gdybyś się zastanawiał. :)
James Wilkins

-3

Zawsze radzono mi unikać sprawdzania w ten sposób i zamiast tego mieć inne zajęcia. Zamiast więc sprawdzać i wykonywać różne czynności w zależności od typu, spraw, aby klasa wiedziała, jak sama przetworzyć ...

np. Obj może być ISpecialType lub IType;

oba mają zdefiniowaną metodę DoStuff (). Dla IType może po prostu zwrócić lub wykonać niestandardowe rzeczy, podczas gdy ISpecialType może zrobić inne rzeczy.

To następnie całkowicie usuwa wszelkie rzutowanie, sprawia, że ​​kod jest czystszy i łatwiejszy w utrzymaniu, a klasa wie, jak wykonywać własne zadania.


1
To nie odpowiada na pytanie. W każdym razie klasy nie zawsze wiedzą, jak sobie radzić z powodu braku kontekstu. Podobną logikę stosujemy do obsługi wyjątków, gdy zezwalamy wyjątkom na przechodzenie w górę łańcucha wywołań, dopóki jakaś metoda / funkcja nie ma wystarczającego kontekstu, aby obsłużyć błędy.
Vakhtang
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.