C# Internals – Method Overloading

Ondřej Janáček       9. 3. 2014       C#, .NET       5663 zobrazení

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

 

hodnocení článku

0       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

Příspěvky zaslané pod tento článek se neobjeví hned, ale až po schválení administrátorem.

                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

Příspěvky zaslané pod tento článek se neobjeví hned, ale až po schválení administrátorem.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback