Function Overloading






2.06/5 (8 votes)
Mar 9, 2007
8 min read

118898
An exploration of function overloading in C#
Introduction
C# allows us to define multiple functions with the same name differing in the number type and order of arguments. This is termed as function overloading. This way, one does not have to remember the names of multiple functions that serve a similar core purpose. However, this can lead to problems. Let's explore.
The Problem
Let's write a simple C# program that implements a class named projector
and contains an entry point function Main()
class Projector
{
public static void Main ()
{
}
}
Now let's make the projector functional by making it capable of displaying a string. To do so we add a function called DisplayString()
as shown below.
class Projector
{
public static void DisplayString (string str)
{
System.Console.WriteLine (str);
}
public static void Main ()
{
DisplayString ("Hello");
}
}
Output
Hello
The above program compiles and runs successfully to display the string "Hello". Now let's enhance the projector
by making it capable of displaying an integer in addition to displaying a string. To do so we add a function called DisplayInteger()
as shown below.
class Projector
{
public static void DisplayString( string str )
{
System.Console.WriteLine( str );
}
public static void DisplayInteger( int number )
{
System.Console.WriteLine( number);
}
public static void Main()
{
DisplayString( "Hello" );
DisplayInteger( 1234 );
}
}
Output
Hello
1234
The above program compiles and runs successfully to display the string "Hello" and the integer "1234". Let's further enhance the projector to display a character. To do so we add a function called DisplayCharacter ()
as shown below.
class Projector
{
public static void DisplayString( string str )
{
System.Console.WriteLine( str );
}
public static void DisplayInteger( int number )
{
System.Console.WriteLine( number);
}
public static void DisplayCharacter( char c )
{
System.Console.WriteLine( c );
}
public static void Main()
{
DisplayString( "Hello" );
DisplayInteger( 1234 );
DisplayCharacter( 'A' );
}
}
Output
Hello
1234
A
The above program compiles and runs successfully to display the string "Hello", the integer "1234" and the character "A". We would refrain from making any further enhancements for the sake of simplicity but would like to make a point that if at all we decide to make further enhancements, the projector
would be equipped with many Display
functions like DisplayBoolean()
, DisplayShort()
, DisplayLong()
, DisplayThis()
and DisplayThat()
etc. But then it would be inconvenient for us to remember the names of so many Display functions. Problem!
The Solution
Because C# has function overloading, one does not have to remember the names of multiple functions that serve a similar core purpose.
The projector
in our example has three functions viz. DisplayString()
, Display Integer()
and DisplayCharacter()
. Their core purpose is to display whatever is passed to them. We can apply function overloading here. We can have three functions named Display()
. The first function will accept a string and display a string. The second function will accept an integer and display an integer. The last function will accept an character and display a character. Thus our enhanced Projector
will now look like this.
class Projector
{
public static void Display( string str )
{
System.Console.WriteLine( str );
}
public static void Display( int number )
{
System.Console.WriteLine( number);
}
public static void Display( char c )
{
System.Console.WriteLine( c );
}
public static void Main()
{
Display( "Hello" );
Display( 1234 );
Display( 'A' );
}
}
Output
Hello
1234
A
The above program compiles and runs successfully to display the string "Hello", the integer "1234" and the character "A". As justified by the output, when the compiler encounters a function call Display("Hello")
it invokes the function Display()
that accepts string str as an argument. When the compiler encounters a function call Display(1234)
it invokes the function Display()
that accepts int number as an argument. When the compiler encounters a function call Display('A')
it invokes the function Display()
that accepts char c as an argument. Thus, you can say that the function Display()
not only displays a string but also bears the load of displaying an integer as well as a character. Hence the name function overloading!
Functions with same name and different return types
Let us understand why there cannot be two or more functions with the same name and different return types within a single class.
Consider the following program
class data
{
string ch;
char ch;
public static void Main(){}
}
Output
P.cs(4,10): error CS0102: The class 'data' already contains a definition
for 'ch'
P.cs(3,10): (Location of symbol related to previous error)
The above program fails to clear the compilation hurdle. At line 3 the compiler is told to treat ch
as string
. At line 4 the compiler is told to treat ch
as char
. The compiler gets confused! What should it treat ch
as? String
or a char
? Hence the error!
Now lets enhance the program by making ch
and empty function.
class data
{
string ch(){}
char ch(){}
public static void Main(){}
}
Output
P.cs(4,10): error CS0111: Class 'data' already defines a member called 'ch'
with the same parameter types
P.cs(3,10): (Location of symbol related to previous error)
The above program fails to clear the compilation hurdle. At line 3 the compiler is told to treat ch
as a string
returning function that accepts no arguments and does nothing. At line 4 the compiler is told to treat ch
as a char returning function that accepts no arguments and does nothing. The compiler gets confused! What should it treat ch
as? A function that returns a string
or a function that returns a char
? Hence the error! Point proved. There cannot be two or more functions with the same name and different return types within a single class.
Function Signatures
We need to study the concept of function signatures to understand the working principle of function overloading.
Consider the following program
class data
{
string ch;
string ch;
public static void Main(){}
}
Output
P.cs(4,10): error CS0102: The class 'data' already contains a definition
for 'ch'
P.cs(3,10): (Location of symbol related to previous error)
The above program fails to clear the compilation hurdle. At line 3 the compiler is told to treat ch
as a string. At line 4 the compiler is AGAIN told to treat ch
as a string. The irritated compiler shouts "Enough! Don't give me the same instructions again and again!"
Now let's enhance the program by making ch
an empty function.
class data
{
string ch(){}
string ch(){}
public static void Main(){}
}
Output
P.cs(4,10): error CS0111: Class 'data' already defines a member called 'ch'
with the same parameter types
P.cs(3,10): (Location of symbol related to previous error)
The above program fails to clear the compilation hurdle. At line 3 the compiler is told to treat ch
as a string-returning function that accepts no arguments and does nothing. At line 4 the compiler is AGAIN told to treat ch
as a string-returning function that accepts no arguments and does nothing. The irritated compiler shouts "Enough! Don't give me the same instructions again and again!"
Now let's enhance the program by making a slight change at line 4. We make the second function display a string "Hello".
class data
{
string ch(){}
string ch(){return "Hello";}
public static void Main(){}
}
Output
P.cs(4,10): error CS0111: Class 'data' already defines a member called 'ch'
with the same parameter types
P.cs(3,10): (Location of symbol related to previous error)
Error again! That too the same one we got in the earlier program. Observe the error carefully! It says that class data already defines a member called ch
with the same parameter types. This means that the compiler is complaining about the parameter types of the function ch ()
. It is not at all complaining about the return type which is string in both the cases. In this case the compiler has nothing to do with what the function ch()
can do. The first version of function ch()
is empty. The successive version of ch()
attempts to return a string "Hello". Despite this difference the compiler is just concerned about the name and type of parameters the function takes. In the above program the compiler understands that at line 3 it is told to treat ch
as a string returning function that accepts no arguments. At line 4 the compiler is AGAIN told to treat ch
as a string-returning function that accepts no arguments. This irritates the complier! The irritated compiler shouts "Enough! Don't give me the same instructions again and again!"
Well, I feel the compiler had had enough of irritation. Now let's enhance the program to make the compiler happy! We make a slight change at line 3. We make the first function ch()
accept and return a string.
class data
{
string ch(string str){ return(str);}
string ch(){return "Hello";}
public static void Main (){}
}
Bingo! The program compiles successfully! So what made the difference? How does the compiler differentiate functions? It differentiates functions by their names, number, type and order of the function arguments. In case the names of the functions are the same, the compiler differentiates them by number, type and order of the function arguments. In the above program we have two functions. Both have the same names i.e ch()
. Since the names are the same, the compiler turns its attention to the number, type and order of the function arguments. The first function ch()
accepts a string str
as an argument. The second function ch()
accepts absolutely nothing. Thus the two functions are totally different.
Thus, a function gets its identity from its name as well as number, type and order of its arguments. And this is the Function Signature. A function signature is composed of function name and number, type and order of its arguments.
To make things more clear lets consider a couple of examples and then move on. Consider the following program.
class data
{
void ch(string str){ System.Console.WriteLine(str);}
void ch(int i){ System.Console.WriteLine(i);}
public static void Main (){}
}
The program compiles successfully! The compiler looks at the first function ch()
as the function that accepts a string and the second function ch()
as the function that accepts an integer. Hence it treats the two functions as two separate functions.
Consider the following program.
class data
{
void ch(string str , int i){ System.Console.WriteLine(str);}
void ch(int i, string str){ System.Console.WriteLine(i);}
public static void Main (){}
}
The program compiles successfully! The compiler looks at the first function ch()
as the function that accepts a string followed by an integer and the second function ch()
as the function that accepts an integer followed by a string. Hence it treats the two functions as two separate functions.
Function Signatures and Access Specifiers and Access Modifiers
Let us study if access specifiers and access modifiers are a part of function signatures.
Consider the following program.
class data
{
void ch(){}
void ch(){}
public static void Main (){}
}
Output
P.cs(4, 8): error CS0111: Class 'data' already defines a member called 'ch'
with the same parameter types
P.cs(3, 8): (Location of symbol related to previous error)
The above program fails to pass the compilation hurdle because there are two functions of the same signature in the same scope. The name of the first function is ch()
and it accepts no arguments. The name of the second function is ch()
and it accepts no arguments.
Let us tamper with the above program by adding a public access specifier to the first ch()
function.
class data
{
public void ch(){}
void ch(){}
public static void Main (){}
}
Output
P.cs(4, 8): error CS0111: Class 'data' already defines a member called 'ch'
with the same parameter types
P.cs(3, 15): (Location of symbol related to previous error)
Error! That too the same one we got in the earlier program. The name of the first function is ch()
and it accepts no arguments. The name of the second function is ch()
and it accepts no arguments. Adding a public access specifier to the first function ch()
didn't make any difference. Thus Access specifiers are not a part of the function signature.
Let us tamper with the above program by making each function accept an integer as an argument.
class data { public void ch(int i){} void ch(int i){} public static void Main (){} }
Output
P.cs(4, 8): error CS0111: Class 'data' already defines a member called 'ch'
with the same parameter types
P.cs(3, 15): (Location of symbol related to previous error)
Error Again! That too the same one we got in the earlier program. The name of the first function is ch()
and it accepts an integer argument. The name of the second function is ch()
and it accepts an integer argument. And by now you very well know that two functions of the same signature are disallowed in the same scope.
Let us tamper with the above program by adding a ref
access modifier to the integer argument of the first function ch()
and out
access modifier to the integer argument of the second ch()
function.
class data
{
public void ch(ref int i){}
void ch(out int i){}
public static void Main (){}
}
Output
P.cs(4,8): error CS0663: 'ch' cannot define overloaded methods which differ
only on ref and out
P.cs(3,15): (Location of symbol related to previous error)
The above program fails to pass the compilation hurdle. The addition of access modifiers to the function arguments has made no difference at all when it comes to function signature. For the compiler, the above program contains two functions of the same signature. Hence the above program fails to compile.
The above examples make it pretty clear that access specifiers and access modifiers are NOT a part of function signatures.