Methods were discussed in the previous post in this series on C# classes. A method is a function member exposed by a class. A method has two major sections: a method header and a method body. The method body contains the sequence of executable code statements inside a block, which has curly braces. They can have local variables, local functions and can call other methods.
You can call other methods from inside a method body. The phrases call a method and invoke a method are synonymous. You call a method by using its name, along with the parameter list.
A method can return a value to the calling code. The returned value is inserted into the calling code at the position in the expression where the invocation occurred. To return a value, the method must declare a return type before the method name. If a method doesn’t return a value, it must declare a return type of void.
Method Overlaoding
Now we will overload our Move method. We will have several Move methods. We decide to overload our Move() method to make it easier for the consumer of our method. The reason for overloading comes down to the consumer.
public class Point { public void Move(int x, int y) {} public void Move(Point newLocation) {} public void Move(Point newLocation, int speed) {} }
.NET
In .NET we encounter many objects that have overloads.
Varying Number of Paramters of the Same Type
Suppose we have a Calculator class that has an Add method that adds varying numbers of integers. We could use overloading to achieve this. This is not an efficient way to do this, but it would look like this.
public class Calculator { public int Add(int n1, int n2){...} public int Add(int n1, int n2, int n3){...} public int Add(int n1, int n2, int n3, int n4){...} ... public int Add(int[], n) {...} }
To make this code better we could have the Add() method take in an array of integers. With this array we can use the method like we see above in the last line of code. Each time we use it however we need to create and initialize an array as shown below in the code.
var result = calculator.Add(new int[] {1, 2, 4, 6, 3})
Params Modifier
The best way to achieve overloading may be the params modifier. It is a lot easier to code and creates the most flexibility for the consumer of the Add method. This was already covered in the functions posts. It is a good thing to know and use when the opportunity arises. Here it is in our classes.
public class Calculator { public int Add(params int[] numbers) { var sum = 0; foreach (var number in numbers) { sum += number; // FYI: a better way is to use LINQ } return sum; } // some examples of how to consume the Add() method var result = Calculator.Add(new int[] {1, 2, 3, 4}) var result = Calculator.Add(1, 2); var result = Calculator.Add(1, 5, 6, 9, 12, 7); // the second and third way are easier to use. Nice.
Ref and Out Modifiers
Basically, do not use these two modifiers unless you have no other choice. Many programmers, including Mosh Hamedani consider these two to be code smells. However, sometimes you do need to use the out modifier. You should probably never use the ref modifier.
The following code works. There is one problem that has to do with best practices.
namespace Methods { class Program { public class Point // a theoretical point on the screen { public int X; public int Y; public Point(int x, int y) // constructor { this.X = x; // property this.Y = y; // property } public void Move(int x, int y) // Move method { // move to a specific x y location this.X = x; this.Y = y; } public void Move(Point newLocation) // Move method { // overloading this.X = newLocation.X; this.Y = newLocation.Y; } } static void Main(string[] args) { var point = new Point(10, 20); point.Move(new Point(40, 60)); Console.WriteLine("X is: {0}", point.X); Console.WriteLine("Y is: {0}", point.Y); point.Move(100, 200); Console.WriteLine("X is: {0}", point.X); Console.WriteLine("Y is: {0}", point.Y); Console.ReadKey(); } } }
What is the best-practices problem? We are duplicating code. Look at our two Move() methods. They do the same thing. They just set the X and Y. We can have one overload of our Move() method call the other Move() method. This way if we have a problem in our code we only have one place to look to fix it. One method overload calls another. One other thing to be aware of. That is null. We could get a Null Reference Exception (NullReferenceException) if we are not careful. Below we show just a couple of changed pieces of code.
public void Move(Point newLocation) // Move method { // if we pass a null object we get an exception if (newLocation == null) throw new ArgumentException("newLocation"); // create a new exception object. An exception is just a class. // pass in 'newLocation' to the constructor Move(newLocation.X, newLocation.Y); // call other Move method } // here is how, in the main program, we pass null var point = new Point(10, 20); point.Move(null); // will throw an exception!
Defensive Programming
The technique used here with the if statement, is called defensive programming. You anticipate errors and code to prevent unhandled exceptions from happening. The idea behind defensive programming is to make code work n g o matter what kind of garbage is passed into it for data. The code should work and produce some kind of result no matter what. Defensive programming improves the robustness of the released code. We really need a global exception handing mechanism to prevent our programs from crashing. We do this with a try block. We do this in the main program.
Offensive Programming
Unfortunately this defensive programming approach also hides errors. Another approach is offensive programming. You could make your method throw a temper tantrum if its input is invalid. If something offends the code, it makes a big deal out of it. The C# keyword checked could be used. However, checked will not be discussed here.
Parse and TryParse
Below is an example of our new code example. We have added a Calculator class. The main() program does three things. It demonstrates Parse and TryParse. The third thing it does it use the Calculator class to add some numbers. The program does not crash, even when we try to convert a string to a number.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Methods { class Program { public class Calculator { public int Add(params int[] numbers) { var sum = 0; foreach (var number in numbers) { sum += number; // FYI: a better way is to use LINQ } return sum; } public class Point // a theoretical point on the screen { public int X; public int Y; public Point(int x, int y) // constructor { this.X = x; // property this.Y = y; // property } public void Move(int x, int y) // Move method { // move to a specific x y location this.X = x; this.Y = y; } public void Move(Point newLocation) // Move method { // if we pass a null object we get an exception //if (newLocation == null) // throw new ArgumentException("newLocation"); // create a new exception object. An exception is just a class. // pass in 'newLocation' to the constructor Move(newLocation.X, newLocation.Y); // call other Move method } } static void Main(string[] args) { // you can use Try (throws exception) try { var num = int.Parse("abc"); } catch (Exception) { // the program does not crash Console.WriteLine("failed to convert!"); } // or you can use TryParse (does not throw exception) int number; var result = int.TryParse("abc", out number); if (result) { Console.WriteLine("success"); } else { // the program does not crash Console.WriteLine("unable to convert to a number."); } UseParams(); Console.ReadKey(); } static void UsePoints() { try { var point = new Point(10, 20); point.Move(null); // will throw an exception! Console.WriteLine("X is: {0}", point.X); Console.WriteLine("Y is: {0}", point.Y); point.Move(100, 200); Console.WriteLine("X is: {0}", point.X); Console.WriteLine("Y is: {0}", point.Y); } catch (Exception) { // just remove the "throw;" if you used a code snippet. } } static void UseParams() { var calcul = new Calculator(); var sum = calcul.Add(1, 2, 3); Console.WriteLine("sum is: {0}", sum); } } } }