Overloading Methods, Overloading Operators, and Returning Multiple Values Using out
AAHC

Click on A to make all fonts on the page smaller.

Click on A to make all fonts on the page larger.

Click on HC to toggle high contrast mode. When you move your mouse over some bold words in high contrast mode, related words are automatically highlighted. Text is shown in black and white.


In this lesson, we'll learn more about how to make methods and operators more versatile with overloading, and we'll return to discuss using the out parameter qualifier.

Overloading
Method Overloading

We've previously seen method overloading, where we use the same name for multiple class methods within the same class, but each identically-named class method differs in the number of parameters, the parameter data types, or both. We identified that the parameter list of a class method is known as the method's fingerprint, or signature. Method overloading is useful in maintaining OOP uniformity of name, while supporting different data types. An example of method overloading is shown in the snippet below, where we create a static class for squaring numbers.

Method Overloading
public static class NumberSquare
{
    public int Square(int value)
    {
        return value * value;
    }
    public long Square(long value)
    {
        return value * value;
    }
    public float Square(float value)
    {
        return value * value;
    }
    public double Square(double value)
    {
        return value * value;
    }
}

With a single method name, Square, we can accommodate a variety of data types.

Operator Overloading

As we create new data types using classes, we typically add a number of class methods that represent the actions we can apply to instances of our class. Wouldn't it be more convenient if we could use the C# operators we're familiar with, like the plus sign (+), to work with our new classes? We can, through operator overloading.

Operator overloading allows us to create classes that work with C# operators. For a list of operators that may be overloaded, along with a few restrictions, see the MSDN article Overloadable Operators. The syntax of overloading an operator is shown in the code snippet below.

Operator Overloading
public class MyNumber
{
    private int _number;
    
    public MyNumber(int number)
    {
        _number = number;
    }
    
    // Overload +, a binary operator, requires two parameters.
    public static MyNumber operator +(MyNumber firstParameter, MyNumber secondParameter)
    {
        firstParameter._number += secondParameter._number;
        return firstParameter;
    }
    
    // Overload !, a unary operator, requires one parameter.
    public static MyNumber operator !(MyNumber firstParameter)
    {
        firstParameter._number *= -1;
        return firstParameter;
    }
}

class TestMyNumber
{
    public TestMyNumber()
    {
        // Create two instances of class.
        MyNumber myNumberOne = new MyNumber(14);
        MyNumber myNumberTwo = new MyNumber(16);
        
        // Use operators.
        MyNumber myNumberThree = myNumberOne + myNumberTwo;
        myNumberThree = !myNumberThree;
        
        // The next line will fail.
        myNumberThree += 5;
    }
}

In our code snippet, we overloaded the plus (+) and not (!) operators. The plus operator is a binary operator, meaning it requires two parameters, whereas the not operator is a unary operator, only requiring a single parameter. Operator overload methods are always declared as public and static, and use the operator keyword before the operator to be overloaded. Overloaded operators also must have a return statement, although the return data type does not have to be the same type as the class. If you do elect to not return the same data type as the class, you'll need to resolve any type errors that result.

In our operator overload example, for each overloaded operator we have to decide what it means to apply that operator to an instance of our class. You can also see our test class that uses the MyNumber class. We also added a line of code that will fail, where we attempt to add the integer value 5 to our class instance. Why does it fail? The integer addition fails because we have not defined an operator overloaded method that has the correct fingerprint of MyNumber with an integer.

Coding

Now we'll create a Bag class that uses overloaded methods to add integer and string data types. We'll also overload the + operator to support adding one Bag to another Bag.

Select File | New | Project. Change the project name to Overloading and click OK.

Click the entry for Form1.cs and change it to Overloading.cs. Change the form title bar's Text property in the Properties Window to Overloading. Click to save your changes.

Modify the form to look like this:

Change each control's Name property to match the Name Property column below, and Text property to match the Text Property column. Arrange the controls similar to the image. When you finish, click to save your changes.

ObjectName PropertyText Property
ListBoxoutputListBox 
ButtonexitButtonExit

Double-click the Exit Button to automatically generate the default event handler code.

Add the usual exitButton_Click() code to Overloading.cs code as shown below:

Overloading.cs
.
.
.        
        private void exitButton_Click(object sender, EventArgs e)
        {
            this.Close();
        }
.
.
.

Add an event handler for the Overloading Form Shown event by selecting the Form (clicking on the Form title bar), selecting the Events icon to view the Events Properties Window, and double-clicking the Shown event.

Now let's add our Bag class.

Right-click on the Overloading project item in the Solution Explorer, and select Add | Class.... In the Add New Item dialog box, change the Name from Class1.cs to Bag.cs, then click Add.

Select the Bag.cs Code Editor, and modify the code as shown below.

Bag.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Overloading
{
    class Bag
    {
        // Class variables
        private List<object> _items;
        
        // Accessors
        public List<object> Items
        {
            get { return _items; }
        }
        
        // Constructors
        public Bag()
        {
            _items = new List<object>();
        }
        
        // Overloaded methods
        public void Add(int contents)
        {
            _items.Add(contents);
        }
        
        public void Add(string contents)
        {
            _items.Add(contents);
        }
        
        // Overloaded operators
        public static Bag operator +(Bag firstBag, Bag secondBag)
        {
            // Make sure we have data in both of our bags.
            if (firstBag != null && secondBag != null)
            {
                // Add contents from second bag to first bag.
                foreach (object contents in secondBag.Items)
                {
                    firstBag.Items.Add(contents);
                }
            }
        
            // Return first bag (including added second bag contents).
            return firstBag;
        }
    }
}

Click to save your changes. We'll add code to Overloading.cs shortly to instantiate the Bag.cs class, but first let's discuss this code.

Bag.cs
.
.
.
        // Overloaded methods
        public void Add(int contents)
        {
            _items.Add(contents);
        }
        
        public void Add(string contents)
        {
            _items.Add(contents);
        }
        
        // Overloaded operators
        public static Bag operator +(Bag firstBag, Bag secondBag)
        {
            // Make sure we have data in both of our bags.
            if (firstBag != null && secondBag != null)
            {
                // Add contents from second bag to first bag.
                foreach (object contents in secondBag.Items)
                {
                    firstBag.Items.Add(contents);
                }
            }
            
            // Return first bag (including added second bag contents).
            return firstBag;
        }
.
.
.

As you can see, we added two overloaded Add methods, one that accepts an integer parameter, and one that accepts a string. Then, we overloaded the plus (+) operator to support adding the contents of one Bag (secondBag) to another Bag (firstBag), and return the expanded Bag. Let's now use our Bag class by creating an instance of the class.

Modify the Overloading.cs code as shown below.

Overloading.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Overloading
{
    public partial class Overloading : Form
    {
        private Bag _myBag = new Bag();
        
        public Overloading()
        {
            InitializeComponent();
        }
        
        private void exitButton_Click(object sender, EventArgs e)
        {
            this.Close();
        }
        private void Overloading_Shown(object sender, EventArgs e)
        {
            // Declare some content data.
            string lastName = "Lastname";
            int age = 21;
            
            // Add the content data to our permanent bag.
            _myBag.Add(age);
            _myBag.Add(lastName);
            
            // Create a temporary bag, and add string and integer literal contents.
            Bag _temporaryBag = new Bag();
            _temporaryBag.Add("Second Bag String");
            _temporaryBag.Add(100);
            
            // Add the contents of our temporary bag to our permanent bag.
            _myBag += _temporaryBag;
            
            // Output the contents of our permanent bag to the ListBox.
            foreach (object contents in _myBag.Items)
            {
                outputListBox.Items.Add(contents.ToString());
            }
        }
    }
}

Click and to save your changes and run the program. You should see this:

Let's discuss how this code works.

Overloading.cs
.
.
.
    public partial class Overloading : Form
    {
        private Bag _myBag = new Bag();
        
        public Overloading()
        {
            InitializeComponent();
        }
        
        private void Overloading_Shown(object sender, EventArgs e)
        {
            // Declare some content data.
            string lastName = "Lastname";
            int age = 21;
            
            // Add the content data to our permanent bag.
            _myBag.Add(age);
            _myBag.Add(lastName);
            
            // Create a temporary bag, and add string and integer literal contents.
            Bag _temporaryBag = new Bag();
            _temporaryBag.Add("Second Bag String");
            _temporaryBag.Add(100);
            
            // Add the contents of our temporary bag to our permanent bag.
            _myBag += _temporaryBag;
            
            // Output the contents of our permanent bag to the ListBox.
            foreach (object contents in _myBag.Items)
            {
                outputListBox.Items.Add(contents.ToString());
            }
        }       
.
.
.

We add a private Bag variable, _myBag, and use the overloaded Add methods to add an integer and a string to _myBag. We create a temporary Bag, _temporaryBag, again adding a string and an integer. We use the overloaded + operator to add our _temporaryBag to our permanent _myBag. Finally, we loop through the contents of _myBag, displaying using our ListBox that the contents of both Bags were indeed added in _myBag.

The out Keyword
Parameter Modifier Review

We've seen that method parameters may have parameter modifers, such as ref, out, and params. We've discussed that the ref keyword is used to modify a method parameter to pass a reference to the parameter argument, rather than a copy of the parameter. We've also discussed the difference between value data types and reference data types, where the ref parameter modifier is required if you want to modify the contents of a parameter and have that change persist in the calling code. We've discussed that reference data types do not require a parameter modifier to have the changes reflected in the calling code, as reference data types by default pass a reference to the allocated memory. And finally, we've discussed how adding the ref parameter modifier to a reference value type would allow us to change the referenced memory, effectively reallocating new memory, and have that new memory reference returned to the calling code. If any of these concepts are vague, feel free to return to the appropriate lessons and review the material. You can also glance at the summary table below. We'll explain more about the table shortly, after we introduce the out keyword.

The out Parameter Modifier

The out keyword works somewhat like ref, in that it forces a pass by reference, returning any changes to the calling code. However, out requires that the parameter be assigned a value within the called code. What does that mean? It means that if you pass in a value data type using out, you must assign a value to the parameter. If you pass in a reference data type using out, you must also assign a value, but for a reference data type; that means you have to create a reference to a memory location, such as when you use the new keyword.

Effectively, ref requires initialization before usage, allowing you to preserve any original data unless you explicitly change it in the called code; out requires value assignment in the called code, removing any prior data that may have been assigned. The table below summarizes the differences.

Type of DataNo Parameter Modifierref Parameter Modifierout Parameter Modifier
Value Data TypesNo changes persist in calling code.Changes persist in calling code, prior data value may remain if not changed, no memory reassignment as value stored on stack.Changes persist in calling code, prior value will be lost, value assignment mandatory, no memory reassignment as value stored on stack.
Reference Data TypesChanges persist in calling code, prior referenced data values may remain if not changed, no memory reassignment allowed.Changes persist in calling code, prior referenced data values may remain if not changed, memory reassignment allowed.Changes persist in calling code, all prior referenced data values lost, memory reassignment mandatory.
Method Fingerprint/Signature and Parameter Modifiers

We do need to emphasize that using a parameter modifier impacts the fingerprint or signature of a method. Typically, an overloaded method must differ by the data type of the parameters, and by the number of parameters. A method signature also will differ based on the use of a parameter modifier. In the code snippet below, the two methods are legitimately different methods because of the use, or non-use, of parameter modifiers.

Method Fingerprint/Signature and Parameter Modifers
public static class NumberSquare
{
    public int Square(int value)
    {
        return value * value;
    }
    
    public void Square(ref int value)
    {
        value *= value;
    }
}

NoteAlthough method signatures may differ based on a parameter modifier, if the only difference between a method signature is ref or out, the method signatures will be considered the same and will produce an error. The code snippet below will produce an error.

Fingerprints Must Differ By More Than Parameter Modifier
public static class NumberSquare
{
    // This code snippet will produce an error as the overloaded method Square signature 
    // must differ by more than ref and out.
    public void Square(ref int value)
    {
        value *= value;
    }

    public void Square(out int value)
    {
        value = 4;
    }
}
Multiple Return Values Using out

As you know, a method can only have a single return value. One practical use of out is to produce the equivalent of multiple return values. We've seen this technique applied before when we use the TryParse method, as shown in the code snippet below. TryParse will return a boolean value to indicate if the conversion was successful or not, and uses the out parameter modifier to return the converted value.

Multiple Return Values Using out
public class Multiple
{
    public Multiple()
    {
        int wages;
        
        if (!int.TryParse("353.26", out wages))
        {
            Console.WriteLine("Error converting value");
        }
    }
}                
Coding

To illustrate the usage of the out parameter modifier, let's modify our Overloading project, adding a Clone method to the Bag class that uses the out parameter modifer, and creating a Clone of our permanent Bag object.

Modify the Bag.cs code as shown below.

Bag.cs
.
.
.
        // Constructors
        public Bag()
        {
            _items = new List<object>();
        }
        
        // Public methods
        public int Clone(out Bag resultBag)
        {
            // Create our new out Bag.
            resultBag = new Bag();
            
            // Copy the contents to our new out Bag.
            foreach (object contents in _items)
            {
                resultBag.Items.Add(contents);
            }
            
            // Return the count of items in our new out Bag.
            return resultBag.Items.Count;
        }
                
        // Overloaded methods
.
.
.

Click to save your changes. Let's discuss how this code works.

Bag.cs
.
.
.
        // Public methods
        public int Clone(out Bag resultBag)
        {
            // Create our new out Bag.
            resultBag = new Bag();
            
            // Copy the contents to our new out Bag.
            foreach (object contents in _items)
            {
                resultBag.Items.Add(contents);
            }
            
            // Return the count of items in our new out Bag.
            return resultBag.Items.Count;
        }
.
.
.

Our Clone method creates a new Bag instance, using the resultBag parameter, iterating through the contents of the _items List, adding each item in the List to our new resultBag. Finally, we return the number of items in the new Bag resultBag object.

Now, let's modify Overloading.cs to use the new Clone method.

Modify the Overloading.cs code as shown below.

Overloading.cs
.
.
.
        private void Overloading_Shown(object sender, EventArgs e)
        {
            // Declare some content data.
            string lastName = "Lastname";
            int age = 21;
            
            // Add the content data to our permanent bag.
            _myBag.Add(age);
            _myBag.Add(lastName);
            
            // Create a temporary bag, and add string and integer literal contents.
            Bag _temporaryBag = new Bag();
            _temporaryBag.Add("Second Bag String");
            _temporaryBag.Add(100);
            
            // Add the contents of our temporary bag to our permanent bag.
            _myBag += _temporaryBag;
            
            // Output the contents of our permanent bag to the ListBox.
            foreach (object contents in _myBag.Items)
            {
                outputListBox.Items.Add(contents.ToString());
            }
            
            // Create a clone of our permanent bag.
            Bag clonedBag;
            int itemsInClone = _myBag.Clone(out clonedBag);
            
            // Output the contents of the cloned bag to the ListBox.
            outputListBox.Items.Add("");
            foreach (object contents in clonedBag.Items)
            {
                outputListBox.Items.Add(contents.ToString());
            }
            
            // Display count of items in cloned bag to the ListBox.
            outputListBox.Items.Add("Cloned count: " + itemsInClone);
        }
.
.
.

Click and to save your changes and run the program. You should see the following output:

Let's discuss how this code works.

Overloading.cs
.
.
.
    // Create a clone of our permanent bag.
    Bag clonedBag;
    int itemsInClone = _myBag.Clone(out clonedBag);
    
    // Output the contents of the cloned bag to the ListBox.
    outputListBox.Items.Add("");
    foreach (object contents in clonedBag.Items)
    {
        outputListBox.Items.Add(contents.ToString());
    }
    
    // Display count of items in cloned bag to the ListBox.
    outputListBox.Items.Add("Cloned count: " + itemsInClone);
.
.
.

We declare a Bag variable called clonedBag. Notice that we don't instantiate the object. We could, but the instance would be destroyed when we pass the clonedBag variable into the Clone method using the out parameter modifier. The Clone method will create a new instance of the Bag class, copying the contents of _items into our new clonedBag object, and returning the count of items in the new clonedBag object saved in itemsInClone. We output the contents of clonedBag, and the itemsInClone, showing that the cloning worked.

Before you move on to the next lesson, do your homework! Right-click in the window where this lesson text appears and select Back. Then select Quiz for this lesson in the syllabus and answer the quiz questions. When you finish the quiz questions, click HAND IT IN at the bottom of that window. Then do the same with the Project(s) for the lesson. Your instructor will grade your quiz(zes) and project(s) and provide guidance if needed.