Algoritmy a programování III

Týden 9

Přetížení metody

Přetížení metody umožňuje více metodám ve stejné třídě mít stejný název, pokud mají jedinečné podpisy. Při kompilaci vyvolání přetížené metody kompilátor používá rozlišení přetížení k určení konkrétní metody k vyvolání. Řešení přetížení najde jednu metodu, která nejlépe odpovídá argumentu. Pokud se nenajde žádná nejlepší shoda, nahlásí se chyba. Následující příklad ukazuje řešení přetížení. Komentář pro každé volání v UsageExample metodě ukazuje, která metoda je vyvolána.


 Console.WriteLine("F()");
    static void F(object x) => Console.WriteLine("F(object)");
    static void F(int x) => Console.WriteLine("F(int)");
    static void F(double x) => Console.WriteLine("F(double)");
    static void F(T x) => Console.WriteLine($"F(T), T is {typeof(T)}");            
    static void F(double x, double y) => Console.WriteLine("F(double, double)");
    
    public static void UsageExample()
    {
        F();            // Invokes F()
        F(1);           // Invokes F(int)
        F(1.0);         // Invokes F(double)
        F("abc");       // Invokes F(T), T is System.String
        F((double)1);   // Invokes F(double)
        F((object)1);   // Invokes F(object)
        F(1);      // Invokes F(T), T is System.Int32
        F(1, 1);        // Invokes F(double, double)
    }
}
" style="box-sizing: inherit; outline-color: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; border: 0px; padding: 0px; line-height: 1.3571; display: block; position: relative;">class OverloadingExample
{
    static void F() => Console.WriteLine("F()");
    static void F(object x) => Console.WriteLine("F(object)");
    static void F(int x) => Console.WriteLine("F(int)");
    static void F(double x) => Console.WriteLine("F(double)");
    static void F<T>(T x) => Console.WriteLine($"F<T>(T), T is {typeof(T)}");            
    static void F(double x, double y) => Console.WriteLine("F(double, double)");
    
    public static void UsageExample()
    {
        F();            // Invokes F()
        F(1);           // Invokes F(int)
        F(1.0);         // Invokes F(double)
        F("abc");       // Invokes F<T>(T), T is System.String
        F((double)1);   // Invokes F(double)
        F((object)1);   // Invokes F(object)
        F<int>(1);      // Invokes F<T>(T), T is System.Int32
        F(1, 1);        // Invokes F(double, double)
    }
}

Jak ukazuje příklad, konkrétní metodu lze vždy vybrat explicitním přetypováním argumentů na přesné typy parametrů a argumenty typu.

Další členy funkce

Členy, které obsahují spustitelný kód, se souhrnně označují jako členy funkce třídy. Předchozí část popisuje metody, které jsou primárními typy členů funkce. Tato část popisuje další druhy členů funkcí podporované jazykem C#: konstruktory, vlastnosti, indexery, události, operátory a finalizační metody.

Následující příklad ukazuje obecnou třídu s názvem MyList<T>, která implementuje rozšiřitelný seznam objektů. Třída obsahuje několik příkladů nejběžnějších druhů členů funkce.



{
    const int DefaultCapacity = 4;    T[] _items;
    int _count;    public MyList(int capacity = DefaultCapacity)
    {
        _items = new T[capacity];
    }    public int Count => _count;    public int Capacity
    {
        get =>  _items.Length;
        set
        {
            if (value < _count) value = _count;
            if (value != _items.Length)
            {
                T[] newItems = new T[value];
                Array.Copy(_items, 0, newItems, 0, _count);
                _items = newItems;
            }
        }
    }    public T this[int index]
    {
        get => _items[index];
        set
        {
            if (!object.Equals(_items[index], value)) {
                _items[index] = value;
                OnChanged();
            }
        }
    }    public void Add(T item)
    {
        if (_count == Capacity) Capacity = _count * 2;
        _items[_count] = item;
        _count++;
        OnChanged();
    }
    protected virtual void OnChanged() =>
        Changed?.Invoke(this, EventArgs.Empty);    public override bool Equals(object other) =>
        Equals(this, other as MyList);    static bool Equals(MyList a, MyList b)
    {
        if (Object.ReferenceEquals(a, null)) return Object.ReferenceEquals(b, null);
        if (Object.ReferenceEquals(b, null) || a._count != b._count)
            return false;
        for (int i = 0; i < a._count; i++)
        {
            if (!object.Equals(a._items[i], b._items[i]))
            {
                return false;
            }
        }
        return true;
    }    public event EventHandler Changed;    public static bool operator ==(MyList a, MyList b) =>
        Equals(a, b);    public static bool operator !=(MyList a, MyList b) =>
        !Equals(a, b);
}
" style="box-sizing: inherit; outline-color: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; border: 0px; padding: 0px; line-height: 1.3571; display: block; position: relative;">public class MyList<T>
{
    const int DefaultCapacity = 4;    T[] _items;
    int _count;    public MyList(int capacity = DefaultCapacity)
    {
        _items = new T[capacity];
    }    public int Count => _count;    public int Capacity
    {
        get =>  _items.Length;
        set
        {
            if (value < _count) value = _count;
            if (value != _items.Length)
            {
                T[] newItems = new T[value];
                Array.Copy(_items, 0, newItems, 0, _count);
                _items = newItems;
            }
        }
    }    public T this[int index]
    {
        get => _items[index];
        set
        {
            if (!object.Equals(_items[index], value)) {
                _items[index] = value;
                OnChanged();
            }
        }
    }    public void Add(T item)
    {
        if (_count == Capacity) Capacity = _count * 2;
        _items[_count] = item;
        _count++;
        OnChanged();
    }
    protected virtual void OnChanged() =>
        Changed?.Invoke(this, EventArgs.Empty);    public override bool Equals(object other) =>
        Equals(this, other as MyList<T>);    static bool Equals(MyList<T> a, MyList<T> b)
    {
        if (Object.ReferenceEquals(a, null)) return Object.ReferenceEquals(b, null);
        if (Object.ReferenceEquals(b, null) || a._count != b._count)
            return false;
        for (int i = 0; i < a._count; i++)
        {
            if (!object.Equals(a._items[i], b._items[i]))
            {
                return false;
            }
        }
        return true;
    }    public event EventHandler Changed;    public static bool operator ==(MyList<T> a, MyList<T> b) =>
        Equals(a, b);    public static bool operator !=(MyList<T> a, MyList<T> b) =>
        !Equals(a, b);
}

Konstruktory

Jazyk C# podporuje konstruktory instancí i statické konstruktory. Konstruktor instance je člen, který implementuje akce potřebné k inicializaci instance třídy. Statický konstruktor je člen, který implementuje akce potřebné k inicializaci samotné třídy při jejím prvním načtení.

Konstruktor je deklarován jako metoda bez návratového typu a se stejným názvem jako obsahující třída. Pokud deklarace konstruktoru static obsahuje modifikátor, deklaruje statický konstruktor. V opačném případě deklaruje konstruktor instance.

Konstruktory instance mohou být přetíženy a mohou mít volitelné parametry. MyList<T> Například třída deklaruje jeden konstruktor instance s jedním volitelným int parametrem. Konstruktory instancí jsou vyvolány pomocí operátoru new . Následující příkazy přidělují dvě MyList<string> instance pomocí konstruktoru MyList třídy s volitelným argumentem a bez něj.


 list1 = new();
MyList list2 = new(10);
" style="box-sizing: inherit; outline-color: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; border: 0px; padding: 0px; line-height: 1.3571; display: block; position: relative;">MyList<string> list1 = new();
MyList<string> list2 = new(10);

Na rozdíl od jiných členů se konstruktory instancí nedědí. Třída nemá žádné konstruktory instance kromě těch, které jsou skutečně deklarovány ve třídě . Pokud není pro třídu zadán žádný konstruktor instance, je automaticky zadán prázdný konstruktor bez parametrů.

Vlastnosti

Vlastnosti jsou přirozeným rozšířením polí. Oba jsou pojmenované členy s přidruženými typy a syntaxe pro přístup k polím a vlastnostem je stejná. Na rozdíl od polí ale vlastnosti neoznamuje umístění úložiště. Místo toho mají vlastnosti přístupové objekty , které určují příkazy spuštěné při čtení nebo zápisu jejich hodnot. Hodnotu načte objekt get accessor . Objekt set accessor zapíše hodnotu .

Vlastnost je deklarována jako pole s tím rozdílem, že deklarace končí na get accessor nebo set accessor zapsaným mezi oddělovači { a } místo toho, aby končila středníkem. Vlastnost, která má přistupovací objekt get i objekt set, je vlastnost pro čtení i zápis. Vlastnost, která má pouze přístupový objekt get, je vlastnost jen pro čtení. Vlastnost, která má pouze objekt set, je vlastnost určená jen pro zápis.

Přístup get odpovídá metodě bez parametrů s návratovou hodnotou typu vlastnosti. Objekt set accessor odpovídá metodě s jedním parametrem s názvem value a bez návratového typu. Objekt get accessor vypočítá hodnotu vlastnosti . Objekt set accessor poskytuje novou hodnotu vlastnosti . Když je vlastnost cílem přiřazení nebo operandem ++ nebo --, je vyvolána přístupová položka sady. V jiných případech, kdy se odkazuje na vlastnost, se vyvolá přístup get.

Třída MyList<T> deklaruje dvě vlastnosti, Count a Capacity, které jsou jen pro čtení a pro čtení i zápis. Následující kód je příkladem použití těchto vlastností:


 names = new();
names.Capacity = 100;   // Invokes set accessor
int i = names.Count;    // Invokes get accessor
int j = names.Capacity; // Invokes get accessor
" style="box-sizing: inherit; outline-color: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; border: 0px; padding: 0px; line-height: 1.3571; display: block; position: relative;">MyList<string> names = new();
names.Capacity = 100;   // Invokes set accessor
int i = names.Count;    // Invokes get accessor
int j = names.Capacity; // Invokes get accessor

Podobně jako pole a metody podporuje jazyk C# vlastnosti instance i statické vlastnosti. Statické vlastnosti jsou deklarovány pomocí statického modifikátoru a vlastnosti instance jsou deklarovány bez něj.

Přístupové objekty vlastnosti můžou být virtuální. Pokud deklarace vlastnosti obsahuje virtualmodifikátor , abstractnebo override , platí to pro přistupová zařízení vlastnosti.

Indexery

Indexer je člen, který umožňuje indexování objektů stejným způsobem jako pole. Indexer je deklarován jako vlastnost s tím rozdílem, že za názvem členu následuje this seznam parametrů zapsaný mezi oddělovače [ a ]. Parametry jsou k dispozici v přístupových objektech indexeru. Podobně jako vlastnosti můžou být indexery pro čtení i zápis, jen pro čtení a jen pro zápis a přístupové objekty indexeru můžou být virtuální.

Třída MyList<T> deklaruje jeden indexer pro čtení i zápis, který přebírá int parametr. Indexer umožňuje indexovat MyList<T> instance s int hodnotami. Například:


 names = new();
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
for (int i = 0; i < names.Count; i++)
{
    string s = names[i];
    names[i] = s.ToUpper();
}
" style="box-sizing: inherit; outline-color: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; border: 0px; padding: 0px; line-height: 1.3571; display: block; position: relative;">MyList<string> names = new();
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
for (int i = 0; i < names.Count; i++)
{
    string s = names[i];
    names[i] = s.ToUpper();
}

Indexery mohou být přetíženy. Třída může deklarovat více indexerů, pokud se počet nebo typy jejich parametrů liší.

Události

Událost je člen, který umožňuje třídě nebo objektu poskytovat oznámení. Událost je deklarována jako pole s tím rozdílem, že deklarace obsahuje event klíčové slovo a typ musí být typ delegáta.

V rámci třídy, která deklaruje člena události, se událost chová stejně jako pole typu delegáta (za předpokladu, že událost není abstraktní a deklaruje přístupové objekty). Pole ukládá odkaz na delegáta, který představuje obslužné rutiny událostí, které byly přidány do události. Pokud nejsou k dispozici žádné obslužné rutiny událostí, pole je null.

Třída MyList<T> deklaruje jeden člen události s názvem Changed, který označuje, že do seznamu byla přidána nová položka nebo že položka seznamu byla změněna pomocí přístupového objektu sady indexerů. Událost Changed je vyvolána OnChanged virtuální metodou, která nejprve zkontroluje, jestli událost je null (to znamená, že nejsou k dispozici žádné obslužné rutiny). Pojem vyvolání události je přesně ekvivalentní vyvolání delegáta reprezentované událostí. Neexistují žádné speciální jazykové konstruktory pro vyvolání událostí.

Klienti reagují na události prostřednictvím obslužných rutin událostí. Obslužné rutiny událostí jsou připojeny pomocí operátoru += a odebrány pomocí operátoru -= . Následující příklad připojí obslužnou rutinu události k Changed události objektu MyList<string>.


();
        names.Changed += new EventHandler(ListChanged);
        names.Add("Liz");
        names.Add("Martha");
        names.Add("Beth");
        Console.WriteLine(s_changeCount); // "3"
    }
}
" style="box-sizing: inherit; outline-color: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; border: 0px; padding: 0px; line-height: 1.3571; display: block; position: relative;">class EventExample
{
    static int s_changeCount;
    
    static void ListChanged(object sender, EventArgs e)
    {
        s_changeCount++;
    }
    
    public static void Usage()
    {
        var names = new MyList<string>();
        names.Changed += new EventHandler(ListChanged);
        names.Add("Liz");
        names.Add("Martha");
        names.Add("Beth");
        Console.WriteLine(s_changeCount); // "3"
    }
}

V pokročilých scénářích, kdy je potřeba řídit základní úložiště události, může deklarace události explicitně poskytnout add a remove přístupové objekty, které se podobají set přistupování vlastnosti.

Operátory

Operátor je člen, který definuje význam použití konkrétního operátoru výrazu na instance třídy. Lze definovat tři druhy operátorů: unární operátory, binární operátory a konverzní operátory. Všechny operátory musí být deklarovány jako public a static.

Třída MyList<T> deklaruje dva operátory operator == a operator !=. Tyto přepsané operátory dávají výrazům, které tyto operátory používají na MyList instance, nový význam. Konkrétně operátory definují rovnost dvou MyList<T> instancí při porovnávání každého z obsažených objektů pomocí svých Equals metod. Následující příklad používá == operátor k porovnání dvou MyList<int> instancí.


 a = new();
a.Add(1);
a.Add(2);
MyList b = new();
b.Add(1);
b.Add(2);
Console.WriteLine(a == b);  // Outputs "True"
b.Add(3);
Console.WriteLine(a == b);  // Outputs "False"
" style="box-sizing: inherit; outline-color: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; border: 0px; padding: 0px; line-height: 1.3571; display: block; position: relative;">MyList<int> a = new();
a.Add(1);
a.Add(2);
MyList<int> b = new();
b.Add(1);
b.Add(2);
Console.WriteLine(a == b);  // Outputs "True"
b.Add(3);
Console.WriteLine(a == b);  // Outputs "False"

První Console.WriteLine výstup, True protože dva seznamy obsahují stejný počet objektů se stejnými hodnotami ve stejném pořadí. Pokud MyList<T> by nebyl definován operator ==, první Console.WriteLine by měl výstup False , protože a a b odkazuje na různé MyList<int> instance.