Method overloading, nebo-li přetěžování metod, jak se toto spojení překládá do češtiny je v .NET frameworku už od samého začátku. Přetěžování metod povoluje více metodám v rámci jednoho typu mít stejné pojmenování, pokud se jinak jejich signatura liší. Která metoda se za běhu vybere, záleží na overload resolution, což je systém vybírající nejvhodnější přetížení na základě předdefinovaných pravidel.
Signatura metody
Pojem signatura metody si můžete představit jako unikátní identifikátor metody v rámci typu (class/struct/interface).
V C# se skládá ze:
- jména metody
- počtů všech typů parametrů
- typem a druhem (ref/out) každého formálního parametru
přičemž parametry jsou identifikovány svojí pozicí, nikoliv jménem. Tedy součástí signatury není:
- modifikátor přístupnosti (public/private/protected)
- návratový typ metody
- jména parametrů
- params modifikátor parametru
- type constraints (where T : class …)
Nicméně, i když jsou ref a out součástí signatury, nesmí se signatura lišit pouze v těchto modifikátorech. Následující kód se zkompiluje s chybou.
void Method(out int x) { }
void Method(ref int x) { }
// toto je už ale v pořádku
void Method(int x) { }
void Method(ref int x) { }
Signatura se také nesmí lišit pouze v typu object a dynamic.
void Method(object x) { }
void Method(dynamic x) { }
Overload Resolution
Vstupem do tohoto systému je seznam argumentů a množina použitelných kandidátů. Pokud množina obsahuje pouze jediného kandidáta, pak je zvolen. Pokud jich obsahuje více, pak je vybrán ten, který je lepší než ostatní. Pokud je nalezeno více, než jeden kandidát, který je lepší než ostatní, pak je výběr nejednoznačný a dostanete chybu.
Přesné definice použitelného a nejlepšího kandidáta jsou poměrně složité a zahrnují mnoho rozhodnutí, a proto zde nebudu zmiňovat (pro vyčerpávající přehled odkazuji na sekci 7.5.3 C# 4.0 specifikace). Klíčová část tohoto procesu je konverze typů argumentů na typy parametrů.
void Method(int x) { }
void Method(double x) { }
// volání metody Method
Method(1.5);
Method(1);
V prvním volání je celkem jasné, které přetížení se vybere, protože hodnota 1.5 není implicitně převeditelná na int (explicitně ano, useknutím desetinných míst), nicméně druhé volání už tak jednoznačné není, protože hodnota 1 je implicitně převeditelná jak na int, tak i double. V tuhle chvíli se kompilátor rozhoduje, co z toho je snazší – konverze int na int nebo int na double? Konverze jakéhokoliv typu sám na sebe je definována vždy jako lepší, než konverze na jiný typ a proto se zavolá první přetížení.
V případě jednoho parametru je to tedy ještě poměrně snadné. Pokud má metoda parametrů více, pak je nutné vybrat nejlepší přetížení tak, že jedno z přetížení musí mít všechny typy argumentů převeditelné na typy parametrů alespoň stejně tak dobře, jako ostatní přetížení a navíc jednu konverzi lepší než ostatní.
void Method(int x, double y) { }
void Method(double x, int y) { }
// volání
Method(1, 1);
Předchozí volání je vyhodnoceno tak, že každé přetížení má jednu lepší konverzi než to druhé, takže je nejednoznačné, které z nich by se mělo vybrat a kompilátor Vás vybídne jedno z nich explicitně určit přetypováním alespoň jednoho z argumentů. Začíná se to trochu komplikovat. Při použití generických parametrů do hry navíc vstupuje type inference, což je systém volby generického typu kompilátorem pokud Vy jej explicitně neuvedete. O tom ale jindy.
Na závěr bych chtěl podotknout, že předchozí vysvětlení jsem se snažil co nejvíce zjednodušit a nevynechat přitom nic důležitého, protože kompletní logika je natolik komplexní, že se neodvažuji zaběhnout do detailů. Funkcím a jejich volání i přetěžování je věnována sekce 7.5 C# specifikace (Function Members) a tam Vás také odkazuji, pokud se chcete dozvědět více. Také přidávám jeden příklad, se kterým jsem se setkal a věřím, že jej již dokážete zdůvodnit.
class Person { }
class Student : Person { }
---
void TestMethodOverloading
{
Get<Student>(new Student()); // které přetížení se použije?
Get(new Student()); // a které teď?
}
void Get<T>(T person) { }
void Get(Person person) { }
Než to zkopírujete do VS, zkuste se nad tím zamyslet :-)
Zdroje: C# language specification, C# in Depth