In this article, you will learn about C# methods (functions), which form the behavior of your classes. C# does not have global functions, but it does have code encapsulation; you will learn how these and other Object Oriented Programming concepts and principles affect what you can do in C#.
C# classes carry their data in the form of fields (private fields, most of the time) and process the data using methods (functions). Methods in C# form the behavior of your class, so the class Employee may have a method called RaiseSalary that raises the salary of the employee based on his/her position in the company.
In C# there are no global functions (a C++ concept), and your methods must exist in the context of a class or struct. What's really interesting is that, when you develop a class and compile it, the other programmers who use it do not need to know anything about how you developed your methods and what happens inside each method; this called code encapsulation. So your algorithms and code are hidden (almost because they can use reverse engineering to know what's going on, but this is not the point now) from the client code.
C# is not case sensitive, so you can have a method called GetSalary() and another one called getsalary(); it doesn't make sense, but this code will compile. Please avoid doing this in your code, though, because it's a bad class design concept. Name your C# methods depending on the process they do.
For example, if you have two methods that update some data, and one of them updates a database and the other updates a file, you can name them UpdateDB() and UpdateFile() respectively. Naming your methods is an important issue in Object Oriented Programming concepts, and you should spend some time doing so in your analysis and design phase.
The first issue we will discuss is the method signature. You begin your method declaration with an access modifier, followed by the static keyword (in the case of a static method), then the return data type. If there is no return data type, use the void keyword instead. This is followed by the method name and the parentheses, which may contain parameters.
Note the use of the return keyword to return a value to the caller. In the case of a void method, you will not be able to return any value from the method.
The next table lists the available access modifiers in C#. I have listed this table before, but I'm listing it again here in case you didn't read the previous articles.
Access Modifier
Description
public
States the class member accessible for all the derived classes, classes in other assemblies and for client code.
private
The opposite of the public Access Modifier, states that the class member is not accessible for any code including derived classes, so the only code that can access (or manipulate) this member is the declared class. Object Oriented Programming provides data protection using the private (and protected) Access Modifiers. For example, you can declare the salary field inside the Employee class as private and it will not be accessible or visible outside this class declaration, so there's no client code that can modify its value unless you, the programmer, want that.
protected
This is an intermediate access level between the public and the private Modifiers. It states that the member is accessible for only derived classes, which gives you the ability to declare fields that are not accessible outside the declared class and any class that derive from it. For example, you can define a class such as ManagerEmployee that derives from and extends the Employee class.
internal
This is an interesting Access Modifier that states that the class member is accessible only for the current assembly (we will discuss assemblies later). This means that the member is public for the assembly, but it's not visible outside the assembly.
So you can write a public method in the following way:
public int GetNumber(int min, int max) { System.Random random = new Random(); return random.Next(min, max); }
This method has two parameters (we will discuss parameters in the second part in detail) that represent the minimum and maximum values that the System.Random class use to generate a random number. The method returns the value that returns from the call to the random.Next method. We will talk more about the GetNumber method soon. Now let's take about the different types of methods.
The different types of methods come from the fact that we have a class and an object of a class. The expression "Instance Method" means that it's a method of an object, and this method will only work on that instance object (not the class). An instance method has direct access to all data inside the instance object, and can modify only this object's data (fields). It has no access to other instance's data and can't affect it directly, so we can say that instance methods have object scope -- this means that they can use the keyword this to access other members of the same instance.
As we said before, you can create instance methods inside C# structures and still use the this keyword. It may not make sense to you, because you might think that the this keyword is a reference to an instance object, so it has an access only for Reference-Types. But when you use it with a structure (Value-Type), it will behave differently. When you use the this keyword on a structure to call an instance method, the value itself will be copied to the stack, and we will have access to this value using the this keyword.
It makes sense now because you are referring to THIS value, inside the method, and the method now can work on the value itself. When the method's execution is complete, the new value will be copied back to its location. This is the way the C# this keyword works, and makes sense with both Reference-Types and Value-Types. Now let's look at an example of instance methods.
using System; namespace MyCompany { public class Methods { static void Main(string[] args) { Employee Michael = new Employee("Michael", "Youssef", 7000m, Position.Engineer); Console.WriteLine(Michael.MonthlySalary); Michael.RaiseSalary(); // another Employee Employee Steven = new Employee("Steven", "Peter", 9000m, Position.Manager); Console.WriteLine(Steven.MonthlySalary); Steven.RaiseSalary();
Console.ReadLine(); } }
public class Employee { public string FirstName; public string LastName; public decimal MonthlySalary; public Position EmpPosition;
public void RaiseSalary() { switch(this.EmpPosition) { case Position.Accountant: Console.WriteLine("The employee salary will be raised 10%"); this.MonthlySalary = this.MonthlySalary * 1.10m; Console.WriteLine("The employee salary now = " + this.MonthlySalary); break; case Position.Lawyer: Console.WriteLine("The employee salary will be raised 15%"); this.MonthlySalary = this.MonthlySalary * 1.15m; Console.WriteLine("The employee salary now = " + this.MonthlySalary); break; case Position.Engineer: Console.WriteLine("The employee salary will be raised 20%"); this.MonthlySalary = this.MonthlySalary * 1.20m; Console.WriteLine("The employee salary now = " + this.MonthlySalary); break; case Position.Manager: Console.WriteLine("The employee salary will be raised 25%"); this.MonthlySalary = this.MonthlySalary * 1.25m; Console.WriteLine("The employee salary now = " + this.MonthlySalary); break; } } }
public enum Position { Accountant = 1, Lawyer, Engineer, Manager } }
Compile the code then run it and you will get the following result:
The code contains the Employee class, the Position enumeration and the Methods class, which contains the Main method. Note that we called the instance method RaiseSalary() using the object, not the class, so in the code we called it twice: once on the Michael object, and another time on the Steven object, to raise their salaries.
Note the use of the Position enumeration as a data type for the field MonthlySalary, and note the use of a switch/case statement in the method RaiseSalary() to indicate the Employee position and raise his/her salary based on that. Also note that we have used the keyword this to refer to the current instance.
Methods process data in one of two ways. The first way is to work on the class fields, like our RaiseSalary() method which works internally on the MonthlySalary field. The second way is to pass values to methods. Methods like Next(int minValue, int maxValue) are called parameterized methods, because they have parameters for the values they work on (process). The value that you pass to the method called argument and the place holder of that value (that declares its type and name) is called a parameter.
In the second part of this article we will discuss Passing arguments by value and by reference. Note that many programmers call the argument a parameter and vice versa. Most commonly we will call them parameters and parameter values (for the argument).
In Object Oriented Programming, all methods must exist in the context of a class (or a structure), but sometimes you will find that there are methods that don't fit into an instance of the class, such as the Console.WriteLine() method. This method is not associated with an instance of a console, and it doesn't make any sense to instantiate an object of the Console class to call the WriteLine() method. At the same time, we don't want to write global functions because it violates the Object Oriented Design Principals.
The solution is to write static methods. A static method has a class scope, not an object scope, so it pertains to the class itself, not to an instance of the class. Actually, you can use static methods in two scenarios. The first scenario occurs when your class contains only functionality where it doesn't make any sense to instantiate an object of that class. So you can declare that your class is sealed, which means that it will not be instantiated, and create all your fields, properties and methods as static to provide the functionality without a violation to the OOP Design Principals. The second scenario is to use a static method in your usual classes to provide functionality that all the objects of that type need. We will modify the Employee class with a static method to understand how that can affect the class.
Another important issue about static methods is, which class members can the static methods access? Of course a static method has access to static fields, static properties and static constructors too. Static methods can't access an instance method; this makes sense because you might have three instance methods and one static method, so the instance methods can access the static method because it's only one known block of code that has a known memory address and behavior, so all instance methods see it. But the static method can't access non-static methods, because at runtime the behavior may do some damage to the object's state, and this is not allowed. Let's look at an example of a static method with our Employee class:
using System; namespace MyCompany { public class Methods { static void Main(string[] args) { Employee Michael = new Employee("Michael", "Youssef", 7000m, Position.Engineer); Console.WriteLine(Michael.MonthlySalary); Michael.RaiseSalary(); // another Employee Employee Steven = new Employee("Steven", "Peter", 9000m, Position.Manager); Console.WriteLine(Steven.MonthlySalary); Steven.RaiseSalary(); Console.WriteLine("--------------------------"); //call to the static method Console.WriteLine("The company has " +Employee.GetEmpNumber()+ " employees");
Console.ReadLine(); } }
public class Employee { public string FirstName; public string LastName; public decimal MonthlySalary; public Position EmpPosition; private static int EmpCounter;
public static int GetEmpNumber() { return Employee.EmpCounter; }
public void RaiseSalary() { switch(this.EmpPosition) { case Position.Accountant: Console.WriteLine("The employee salary will be raised 10%"); this.MonthlySalary = this.MonthlySalary * 1.10m; Console.WriteLine("The employee salary now = " + this.MonthlySalary); break; case Position.Lawyer: Console.WriteLine("The employee salary will be raised 15%"); this.MonthlySalary = this.MonthlySalary * 1.15m; Console.WriteLine("The employee salary now = " + this.MonthlySalary); break; case Position.Engineer: Console.WriteLine("The employee salary will be raised 20%"); this.MonthlySalary = this.MonthlySalary * 1.20m; Console.WriteLine("The employee salary now = " + this.MonthlySalary); break; case Position.Manager: Console.WriteLine("The employee salary will be raised 25%"); this.MonthlySalary = this.MonthlySalary * 1.25m; Console.WriteLine("The employee salary now = " + this.MonthlySalary); break; } } }
public enum Position { Accountant = 1, Lawyer, Engineer, Manager } }
The result is:
We have added a private static field (EmpCounter) and a static method (GetEmpNumber()) which returns the total number of our employees by returning the value of the EmpCounter static field. Note that we increase the EmpCounter field by 1 in the Employee's constructor, which proves that the instance method (in our case the Constructor) accesses the static field and increases it by 1. Note that we use the class name in order to access the static member, and within the static methods you can't use the this keyword, because this refers to the current instance, which is not the case with a static member.
A class such as the Console class is called a group abstraction, which means that it contains static members only, and it can't be instantiated. This is an important design issue, and you must follow this design; when you have a class that contains a lot of instance methods and static methods, try to separate the static methods into a group abstraction class, and put the instance method into another class. It's usual to have something like two or three static members in your class, but if you have 20, it's better to follow the group abstraction design and make that class sealed.
When the CLR calls a method, there are steps for executing the internal method's code. The first step is to allocate a memory space called a Stack Frame to hold the method's local variables (including the argument values that has been passed to the method). The Stack Frame is a block of memory that the CLR allocates just before calling the method, and it will be dislocated just after the execution of the method ends, so you can say that it's a temporary block of memory. The CLR calculates the space of the Stack Frame based on the number and the Data Type of the method's parameters and local variables, so you don't have to know anything about it, but we discuss it here to improve your knowledge.
Inside a method you will find a call to another method, and so on; it's basic Object oriented Programming Principles that methods call other methods. The CLR keeps track of what methods have been called inside your method, and so on, by building a stack trace, which is an ordered collection of the stack frames that have been created so far for the current method.
Actually, the Debugging Features of Microsoft Visual Studio.NET use the StackFrame and StackTrace classes extensively to provide you with information for what methods have been called, along with the values of the arguments. These classes lives in the namespace System.Diagnostics, and you can use it to navigate through the method calls that happened in the application. Let's look at an example:
static void Method4() { StackTrace st = new StackTrace(); for(int i=st.FrameCount-1; i >= 0 ; i--) { StackFrame sf = new StackFrame(); sf = st.GetFrame(i); Console.WriteLine(sf.GetMethod()); } } } }
The result is:
As you can see, we received the order of the method calls that have been called in an ordered list. The Main method had a call to Method1, which in turn called Method2, which in turn called Method3, which in turn called Method4 -- and in Method4 we create a StackTrace object and then loop on the StackTrace object to print each StackFrame that has been created for each method. Actually, if you take a look at the for loop, you will find that I use a reserve order to print the method calls exactly as we did through the code, but some programmers write the code in the following way, which prints the Method4 first (because it's the one that has created the code):
static void Method4() { StackTrace st = new StackTrace(); for(int i = 0; i < st.FrameCount; i++) { StackFrame sf = new StackFrame(); sf = st.GetFrame(i); Console.WriteLine(sf.GetMethod()); } }
The result will be
By using the namespace System.Diagnostics, you can create debugging tools, and you may create a runtime debugger for your application, which is very useful in scientific applications.
Method overloading is an Object oriented Design Principle. Sometimes you will have a behavior that you need to encapsulate inside a method that takes two parameters of type integer, and at another point in your application you need another method to concatenate two strings, and it's nice to name it. Ahh, but how?
In Object Oriented Programming, you can overload the method with another variation, which means that you will provide another method (normally with another implementation) with the same name, but it MUST differ in the number of the parameters, or it can differ in the parameter's data type, or maybe the parameter's order, so it's a way for the C# Compiler to differentiate between the method variations.
You can't use the method return type to make a new variation of the method, and you also can't overload a method with a property, because they are two different class members. When you use Visual Studio.NET, you can see how its intelliSense feature displays the method overloads; it will display the current method variation's parameter list with a number to indicate how many variations of this method exist.
The following screen shot illustrates the Visual Studio.NET intelliSense feature of the method Console.WriteLine()
There are 19 variations of the method WriteLine and, as you can see, there are arrows so that you can navigate through the variations and learn more about them. Also, there is a description for each parameter along with its data type. So let's overload the Add method.
public class Number { public static int Add(int x, int y) { return x + y; }
public static int Add(int x, int y,int z) { return x + y + z; }
public static double Add(double x, double y) { return x + y; }
public static double Add(double x, double y, double z) { return x + y + z; }
public static string Add(string x, string y) { return x +" "+ y; } } }
The result is:
As you can see, the code has been compiled, and we have five different versions of the Add method that differ in the parameter's data type, and some of them in the parameter's number. Note the use of the d letter after the value to denote a double literal value, so it can be differentiated from integer values, and so on.
In the second part, after we discuss passing parameters by value and by reference, we will see that ref and out keyword can't overload a method.