Algoritmy a programování III

Týden 8

Statické metody a metody instancí

Metoda deklarovaná modifikátorem static je statická metoda. Statická metoda nepracuje s konkrétní instancí a má přímý přístup pouze k statickým členům.

Metoda deklarovaná bez modifikátoru static je metoda instance. Metoda instance pracuje s konkrétní instancí a má přístup k statickým i členům instance. K instanci, na které byla vyvolána metoda instance, lze explicitně přistupovat jako this. Jedná se o chybu, na this které se odkazuje ve statické metodě.

Následující Entity třída má statické členy i členy instance.


class Entity
{
    static int s_nextSerialNo;
    int _serialNo;
    
    public Entity()
    {
        _serialNo = s_nextSerialNo++;
    }
    
    public int GetSerialNo()
    {
        return _serialNo;
    }
    
    public static int GetNextSerialNo()
    {
        return s_nextSerialNo;
    }
    
    public static void SetNextSerialNo(int value)
    {
        s_nextSerialNo = value;
    }
}

Každá Entity instance obsahuje sériové číslo (a pravděpodobně i některé další informace, které tady nejsou uvedené). Konstruktor Entity (který je jako metoda instance) inicializuje novou instanci s dalším dostupným sériovým číslem. Vzhledem k tomu, že konstruktor je členem instance, je povolen přístup k poli instance i _serialNo statickému s_nextSerialNo poli.

GetNextSerialNo Statické metody a SetNextSerialNo mají přístup ke statickému s_nextSerialNo poli, ale při přímém přístupu _serialNo k poli instance by došlo k chybě.

Následující příklad ukazuje použití Entity třídy .


Entity.SetNextSerialNo(1000);
Entity e1 = new();
Entity e2 = new();
Console.WriteLine(e1.GetSerialNo());          // Outputs "1000"
Console.WriteLine(e2.GetSerialNo());          // Outputs "1001"
Console.WriteLine(Entity.GetNextSerialNo());  // Outputs "1002"

Statické SetNextSerialNo metody a GetNextSerialNo jsou vyvolány ve třídě, zatímco GetSerialNo metoda instance je vyvolána na instancích třídy .

Virtuální, přepsání a abstraktní metody

Pomocí virtuálních, přepsání a abstraktních metod můžete definovat chování pro hierarchii typů tříd. Vzhledem k tomu, že třída může být odvozena ze základní třídy, mohou tyto odvozené třídy potřebovat změnit chování implementované v základní třídě. Virtuální metoda je metoda deklarovaná a implementovaná v základní třídě, kde každá odvozená třída může poskytovat konkrétnější implementaci. Metoda override je metoda implementovaná v odvozené třídě, která upravuje chování implementace základní třídy. Abstraktní metoda je metoda deklarovaná v základní třídě, která musí být přepsána ve všech odvozených třídách. Abstraktní metody ve skutečnosti nedefinuje implementaci v základní třídě.

Volání metod instance se může přeložit na implementaci základní třídy nebo odvozené třídy. Typ proměnné určuje její typ v době kompilaceTyp kompilace je typ, který kompilátor používá k určení svých členů. Proměnná však může být přiřazena instanci libovolného typu odvozeného z jejího typu kompilaceTyp runtime je typ skutečné instance, na které proměnná odkazuje.

Při vyvolání virtuální metody typ za běhu instance, pro kterou se toto vyvolání provádí, určuje skutečnou implementaci metody, která se má vyvolat. Při vyvolání nevirtuální metody je určujícím faktorem typ kompilace instance.

Virtuální metodu lze přepsat v odvozené třídě. Pokud deklarace metody instance obsahuje modifikátor přepsání, metoda přepíše zděděnou virtuální metodu se stejným podpisem. Deklarace virtuální metody zavádí novou metodu. Deklarace metody přepsání se specializuje na existující zděděnou virtuální metodu tím, že poskytuje novou implementaci této metody.

Abstraktní metoda je virtuální metoda bez implementace. Abstraktní metoda je deklarována s modifikátorem abstract a je povolena pouze v abstraktní třídě. Abstraktní metoda musí být přepsána v každé ne abstraktní odvozené třídě.

Následující příklad deklaruje abstraktní třídu , Expressionkterá představuje uzel stromu výrazů, a tři odvozené třídy, ConstantVariableReferenceOperation, které implementují uzly stromu výrazů pro konstanty, odkazy na proměnné a aritmetické operace. (Tento příklad je podobný typu, ale nesouvisí s typy stromu výrazů).


 vars);
}public class Constant : Expression
{
    double _value;
    
    public Constant(double value)
    {
        _value = value;
    }
    
    public override double Evaluate(Dictionary vars)
    {
        return _value;
    }
}public class VariableReference : Expression
{
    string _name;
    
    public VariableReference(string name)
    {
        _name = name;
    }
    
    public override double Evaluate(Dictionary vars)
    {
        object value = vars[_name] ?? throw new Exception($"Unknown variable: {_name}");
        return Convert.ToDouble(value);
    }
}public class Operation : Expression
{
    Expression _left;
    char _op;
    Expression _right;
    
    public Operation(Expression left, char op, Expression right)
    {
        _left = left;
        _op = op;
        _right = right;
    }
    
    public override double Evaluate(Dictionary vars)
    {
        double x = _left.Evaluate(vars);
        double y = _right.Evaluate(vars);
        switch (_op)
        {
            case '+': return x + y;
            case '-': return x - y;
            case '*': return x * y;
            case '/': return x / y;
            
            default: throw new Exception("Unknown operator");
        }
    }
}
" 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 abstract class Expression
{
    public abstract double Evaluate(Dictionary<string, object> vars);
}public class Constant : Expression
{
    double _value;
    
    public Constant(double value)
    {
        _value = value;
    }
    
    public override double Evaluate(Dictionary<string, object> vars)
    {
        return _value;
    }
}public class VariableReference : Expression
{
    string _name;
    
    public VariableReference(string name)
    {
        _name = name;
    }
    
    public override double Evaluate(Dictionary<string, object> vars)
    {
        object value = vars[_name] ?? throw new Exception($"Unknown variable: {_name}");
        return Convert.ToDouble(value);
    }
}public class Operation : Expression
{
    Expression _left;
    char _op;
    Expression _right;
    
    public Operation(Expression left, char op, Expression right)
    {
        _left = left;
        _op = op;
        _right = right;
    }
    
    public override double Evaluate(Dictionary<string, object> vars)
    {
        double x = _left.Evaluate(vars);
        double y = _right.Evaluate(vars);
        switch (_op)
        {
            case '+': return x + y;
            case '-': return x - y;
            case '*': return x * y;
            case '/': return x / y;
            
            default: throw new Exception("Unknown operator");
        }
    }
}

Předchozí čtyři třídy lze použít k modelování aritmetických výrazů. Například při použití instancí těchto tříd může být výraz x + 3 reprezentován následujícím způsobem.


Expression e = new Operation(
    new VariableReference("x"),
    '+',
    new Constant(3));

Metoda EvaluateExpression instance je vyvolána k vyhodnocení daného výrazu double a vytvoření hodnoty. Metoda přebírá Dictionary argument, který obsahuje názvy proměnných (jako klíče položek) a hodnoty (jako hodnoty položek). Vzhledem k tomu, že Evaluate je abstraktní metoda, musí ne abstraktní třídy odvozené z Expression přepisovat Evaluate.

Implementace Evaluate typu A Constantjednoduše vrátí uloženou konstantu. VariableReferenceImplementace objektu vyhledá název proměnné ve slovníku a vrátí výslednou hodnotu. Implementace Operationnejprve vyhodnotí levé a pravé operandy (rekurzivním vyvoláním jejich Evaluate metod) a pak provede danou aritmetickou operaci.

Následující program používá Expression třídy k vyhodnocení výrazu x * (y + 2) pro různé hodnoty x a y.


 vars = new();
vars["x"] = 3;
vars["y"] = 5;
Console.WriteLine(e.Evaluate(vars)); // "21"
vars["x"] = 1.5;
vars["y"] = 9;
Console.WriteLine(e.Evaluate(vars)); // "16.5"
" 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;">Expression e = new Operation(
    new VariableReference("x"),
    '*',
    new Operation(
        new VariableReference("y"),
        '+',
        new Constant(2)
    )
);
Dictionary<string, object> vars = new();
vars["x"] = 3;
vars["y"] = 5;
Console.WriteLine(e.Evaluate(vars)); // "21"
vars["x"] = 1.5;
vars["y"] = 9;
Console.WriteLine(e.Evaluate(vars)); // "16.5"