So far we have learned about declaring and using classes, creating objects, writing and calling methods and using constructors and destructor. Now in this second part, we are going to cover few other topics related to class designing in C#.
Access Modifiers
We have discussed a bit of the term "access modifier" in the last chapter. Here, we are going to understand this in details. An access modifier is used to restrict the accessibility of class members outside the class. Precisely, there are four types of access modifiers provided by C# language. There are:
- private : The members of a class that are declared with private access modifier are accessible within the containing class. If we try to access the private members of a class outside the class then it will be a compile time error.
- public : The members of a class declared with public access modifier are accessible to all other classes. Under no conditions these members are restricted to usage.
- protected : Members declared with protected access modifier can be accessed within the containing class and the classes derived by the containing class. We will talk about the "protected" access modifier more when we will cover the concept of derived class later in this course.
- internal : Members declared with internal access modifier are accessible to all other classes within same assembly. We will talk about the "internal" access modifier more we will cover Assemblies later in this course.
Apart from these four access modifiers, couple of combination of these modifiers are also allowed. There are:
- protected internal : Combining these two access modifiers, we can say that the members declared with "protected internal" are accessible to classes within same assembly or classes derived by the containing class (within or outside same assembly).
- private protected : Members declared with "private protected" are accessible within the containing class, classes derived by the containing class within the current assembly.
The access modifiers protected, internal, protected internal and private protected will be covered later in this course. Let's see private and public access modifiers with the help of an example.
using System;
class Student
{
private string _name;
private int _age;
public Student(string name, int age)
{
_name = name;
_age = age;
}
public string GetName()
{
return _name;
}
public string GetAge()
{
return _age;
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Using public and private access modifiers");
Console.WriteLine("");
Student alice = new Student("Alice", 26);
// Console.WriteLine($"{alice._name}'s age is {alice._age} years.");
Console.WriteLine($"{alice.GetName()}'s age is {alice.GetAge()} years.");
Console.Write("Press any key to continue...");
Console.ReadKey(true);
}
}
The following line will show a compile time error because the data members "_name" and "_age" are private.
Console.WriteLine($"{alice._name}'s age is {alice._age} years.");
The below line will not show any compile time error and will execute successfully because the methods "GetName()" and "GetAge()" are public and hence accessible outside the class as well.
Console.WriteLine($"{alice.GetName()}'s age is {alice.GetAge()} years.");
The "ref" and "out" keywords
We have learnt that how to pass arguments to the methods. The "ref" and "out" keywords adds additional behaviour to the variables that are being passed in the method as arguments. We have also learnt that there are two types of data types - value type and reference type. Usually, when we pass value types as arguments to the method, a copy of that variable is passed to the method body. The "ref" keyword is used to change this behaviour. The "ref" keyword makes the actual reference of the value type variable to be passed to the method body instead of the copy of that value. Let's understand this with the help of an example.
We will try to implement a program that can swap two integers. First, In the below example we will try to see the usual behaviour of value type arguments in a method.
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Pass by value");
Console.WriteLine("");
Program p = new Program();
int a = 10;
int b = 20;
p.Swap(a, b);
Console.WriteLine($"a = {a}, b = {b}");
Console.Write("Press any key to continue...");
Console.ReadKey(true);
}
void Swap(int a, int b)
{
var temp = a;
a = b;
b = temp;
}
}
The above program will not swap the integers "a" and "b". It will print "a = 10, b = 20" to the output console. The is because the copies of variables "a" and "b" were passed to the Swap(..) method and the Swap(..) method swapped the copies of the variables and not the original variables. This is also called "Pass by value".
Now, we will use "ref" keyword to change this behaviour so that the reference to the actual variables will be passed and not the copies. Let's see this in the example below.
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Pass by reference");
Console.WriteLine("");
Program p = new Program();
int a = 10;
int b = 20;
p.Swap(ref a, ref b);
Console.WriteLine($"a = {a}, b = {b}");
Console.Write("Press any key to continue...");
Console.ReadKey(true);
}
void Swap(ref int a, ref int b)
{
var temp = a;
a = b;
b = temp;
}
}
The above program is almost the same as the first one. The only change being we changed the method arguments from Swap(int a, int b) to Swap(ref int a, ref int b) and called it with Swap(ref a, ref b) instead of Swap(a, b). Now, the program will print "a = 20, b = 10" to the output console. This means the Swap(..) method swapped the values of the original variable. This is also called "Pass by reference".
The "out" keyword or parameter modifier is used when we want to assign a value to a variable outside the method. This can also be done using the "ref" keyword. However, the difference is that the "ref" variable must be assigned a value before passing to a method. In the case of the "out" keyword, it is not necessary to assign any value to a variable that we are going to pass to a method with an "out" keyword.
However, it is always necessary to assign a value to the "out" variable inside the method definition. It will be an error otherwise.
It is generally used when we want to output some additional information from the method other than the return value. For example, the Int32 class contains a method called TryParse(..) that tries to parse a string value to an integer data type. It has the following signature.
public static bool TryParse (string s, out int result);
If the parsing succeeds, it will return "true" and outputs the result value as an "out" parameter. Let's see an example here.
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("The out keyword");
Console.WriteLine("");
int result;
bool isValid = int.TryParse("45", out result);
if(isValid)
{
Console.WriteLine($"result = {result}");
}
else
{
Console.WriteLine("Not a valid string");
}
// Console.Write("Press any key to continue...");
// Console.ReadKey(true);
}
}
Note: Please note that the variable "result" can be declared inline with method calling statement as mentioned below.
bool isValid = int.TryParse("45", out int result);
Object initializers
The Object initializer is a concise way to initialize variables and properties without having to explicitly assign with objects or initializing under class constructors. Let's see with an example.
using System;
class Student
{
public string Name;
public int Age;
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Optional arguments");
Console.WriteLine("");
Student alice = new Student { Name = "Alice", Age = 26 };
Console.WriteLine($"{alice.Name}'s age is {alice.Age} years.");
Console.Write("Press any key to continue...");
Console.ReadKey(true);
}
}
In the above example, we have initialized the variables "Name" and "Age" with the statement "new Student { Name = "Alice", Age = 26 };" and it works as expected. This syntax is called object initializer and is frequently used while working with Properties and anonymous types which we will cover in detail later in this course.
Optional Arguments
Optional Arguments as the name says are those method arguments which need not necessary be passed while calling a method. This gives more flexibility to the method definition and functionality. Not only methods but constructors can also have optional arguments. Let's see an example here.
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Optional arguments");
Console.WriteLine("");
Shape shape = new Shape();
shape.DrawBox(80, 60);
shape.DrawBox(100, 50, "Red");
Console.Write("Press any key to continue...");
Console.ReadKey(true);
}
}
class Shape
{
public void DrawBox(int length, int width, string color = "Black")
{
Console.WriteLine($"Drawing Box of length = {length} and width = {width} having border color as {color}");
}
}
In the above program, as we can see, we have called the DrawBox(..) method in two different ways. First with two arguments where we specified length and width. Second with three arguments where we specified length, width and color. The third argument color is an optional argument. If we look at the method definition, we have specified a default color "Black". This is how we make an optional argument by specifying a default value at the method definition.
* Please note that all optional parameters must be specified after all required parameters.
Named Arguments
Named arguments are often useful to add readability to the calling method. Generally, we have to provides method arguments as per the position mentioned in the method definition. For example, consider the below method definition.
void PrintCharacters(char character, int length, int breakAt)
{
....
}
To call the above method, we need to pass the parameters in accordance to the sequence of method definition like first a character, then second an integer to specify the length and then another integer to specify the breakAt parameter.
PrintCharacters('-', 100, 50);
We can not write the following line of code.
// PrintCharacters(100, '-', 50);
However, it is possible to change the sequence of argument by specifying names as mentioned in below example.
PrintCharacters(length: 100, character: '-', breakAt: 50);
There is one justified use case is when we want to mention few of multiple optional arguments. Let's see with the help of an example.
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Named arguments");
Console.WriteLine("");
Publisher publisher = new Publisher();
publisher.PublishPost(postName: "Named Arguments", authorName: "Satya", publisherName: "Witscad", NumberOfPages: 4, genre: "Programming");
Console.Write("Press any key to continue...");
Console.ReadKey(true);
}
}
class Publisher
{
public void PublishPost(string authorName, string postName, string publisherName, int NumberOfPages, string publishedDate = "01-01-2019", string genre = "General", string coAuthorName ="N/A")
{
Console.WriteLine($"Publishing post - {postName} under {genre} category | {publishedDate} | {NumberOfPages} pages");
Console.WriteLine($"Author : {authorName}");
if(coAuthorName != "N/A")
{
Console.WriteLine($"Co-author : {coAuthorName}");
}
Console.WriteLine($"Publisher : {publisherName}");
}
}
In the above example, there are three optional parameters. Let's say we want to pass only one optional argument "genre". Without named arguments, it would not have been possible to pass this second optional argument "genre" because there is one optional argument prior to "genre" in the sequence which is "publishedDate". Hence, with the help of named argument, it is possible to specify the desired optional arguments irrespective of sequence.
The "this" keyword
The "this" keyword is an automatic reference to the instance of a class within the class. It comes very handy when we have same data member names and method (or constructor) parameter names, then to distinguish between them, we refer the "this" keyword. So, let's pull an example from previous chapter and modify it.
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Using this keyword");
Console.WriteLine("");
House house = new House(50, 40);
double area = house.Area();
Console.WriteLine("Area of the house = " + area);
Console.Write("Press any key to continue...");
Console.ReadKey(true);
}
}
class House
{
int length;
int width;
//constructor
public House(int length, int width)
{
this.length = length;
this.width = width;
}
public int Area()
{
int area = length * width;
return area;
}
}
If you see the constructor of the House class, it takes two parameters length and width. Our data members have also the same name. So without "this" keyword, it would not have been possible to write parameters with similar names. In this scenario having same name makes perfect sense from readability perspective. With "this" keyword, we are able to distinguish between the parameter names and data member names as we could write "this.length" and "this.width" to refer to these instance variables. Since "this" holds the reference of an instance of the class, we can refer any instance data member or methods in our class with "this" keyword wherever necessary.
The "static" keyword
Before describing what "static" is in context to C#, we need to understand classes and their instances from a different perspective.
When an instance of that class is created, it takes a portion of memory in the CLR memory heap. When a class is declared, all the static members takes a portion of memory in the CLR a different memory heap called "High-frequency heap". The details about "High-frequency heap" are out of the scope of this course but you can anyways check this out over the internet if you like to dig deep. So, this means that both class and its instances are available in the memory for use.
We know how to create an instance and use its data members and methods. To use data members and methods by referring to the class itself (and not instance), we use the "static" keyword. Let's see with an example.
using System;
class Canvas
{
public static int width;
public static int height;
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("The static keyword");
Console.WriteLine("");
Canvas.width = 100;
Canvas.height = 250;
Console.WriteLine($"The Canvas dimension is {Canvas.height} x {Canvas.width}");
Console.Write("Press any key to continue...");
Console.ReadKey(true);
}
}
If you see the program above, we have not created any instance of class "Canvas". However, the use of static keyword made the variables "width" and "height" to get associated with class itself. This allows us to have a flexibility where only one instance is required throughout the program. So instead of creating an instance, we can use the data members and methods by referring the class itself.
In the above example, we have used static variable. Likewise we can have static methods and constructor in a class. Let's extend the above example to include these two.
using System;
class Canvas
{
static int width;
static int height;
static Canvas()
{
width = 100;
height = 180;
}
public static void GetDimensions()
{
Console.WriteLine($"Canvas is ready with dimensions {height} x {width}");
}
public static void SetDimensions(int w, int h)
{
width = w;
height = h;
Console.WriteLine($"Canvas is ready with new dimensions {height} x {width}");
}
public static void DrawCircle(int radius, int x, int y, string color)
{
Console.WriteLine($"Drawing circle with radius {radius} pixels at position ({x}, {y}) with {color} color.");
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("The static keyword");
Console.WriteLine("");
Canvas.GetDimensions();
Canvas.SetDimensions(100, 250);
Canvas.DrawCircle(20, 15, 25, "red");
Console.Write("Press any key to continue...");
Console.ReadKey(true);
}
}
If you note, we have not called the constructor in our program. The reason is that we can not call static constructor. It is automatically invoked as soon as the class is loaded in the CLR. Also, the static constructor can not have parameters or access modifiers.
A class can itself be declared as static. In this case, we are not allowed to create an instance of the class. Also, a static class can only have static members. It is useful to write utility classes where creating an instance of class makes no sense while designing object oriented systems. For e.g. .Net framework contains a static class System.Math that contains static methods Min, Max etc that we works in a same way anywhere given same method arguments.