In this part of the C# tutorial, we cover object oriented programming in C#.
There are three widely used programming paradigms: procedural programming, functional programming and object-oriented programming. C# supports both procedural and object-oriented programming.
OOP definition
Object-oriented programming (OOP) is a programming paradigm that uses objects and their interactions to design applications and computer programs.
There are some basic programming concepts in OOP:
- Abstraction
- Polymorphism
- Encapsulation
- Inheritance
The abstraction is simplifying complex reality by modeling classes appropriate to the problem. The polymorphism is the process of using an operator or function in different ways for different data input. The encapsulation hides the implementation details of a class from other objects. The inheritance is a way to form new classes using classes that have already been defined.
C# objects
Objects are basic building blocks of a C# OOP program. An object is a combination of data and methods. The data and the methods are called members of an object. In an OOP program, we create objects. These objects communicate together through methods. Each object can receive messages, send messages and process data.
There are two steps in creating an object. First, we define a class. A class is a template for an object. It is a blueprint which describes the state and behavior that the objects of the class all share. A class can be used to create many objects. Objects created at runtime from a class are called instances of that particular class.
using System; var b = new Being(); Console.WriteLine(b); class Being {}
In our first example, we create a simple object.
class Being {}
This is a simple class definition. The body of the template is empty. It does not have any data or methods.
var b = new Being();
We create a new instance of the Being
class. For this we have the new
keyword. The b
variable is the handle to the created object.
Console.WriteLine(b);
We print the object to the console to get some basic description of the object. What does it mean, to print an object? When we print an object, we in fact call its ToString
method. But we have not defined any method yet. It is because every object created inherits from the base object
. It has some elementary functionality which is shared among all objects created. One of this is the ToString
method.
$ dotnet run Being
C# object attributes
Object attributes is the data bundled in an instance of a class. The object attributes are called instance variables or member fields. An instance variable is a variable defined in a class, for which each object in the class has a separate copy.
using System; var p1 = new Person(); p1.name = "Jane"; var p2 = new Person(); p2.name = "Beky"; Console.WriteLine(p1.name); Console.WriteLine(p2.name); class Person { public string name; }
In the above C# code, we have a Person
class with one member field.
class Person { public string name; }
We declare a name member field. The public
keyword specifies that the member field will be accessible outside the class block.
var p1 = new Person(); p1.name = "Jane";
We create an instance of the Person
class and set the name variable to "Jane". We use the dot operator to access the attributes of objects.
var p2 = new Person(); p2.name = "Beky";
We create another instance of the Person
class. Here we set the variable to "Beky".
Console.WriteLine(p1.name); Console.WriteLine(p2.name);
We print the contents of the variables to the console.
$ dotnet run Jane Beky
Each instance of the Person
class has a separate copy of the name member field.
C# methods
Methods are functions defined inside the body of a class. They are used to perform operations with the attributes of our objects. Methods bring modularity to our programs.
Methods are essential in the encapsulation concept of the OOP paradigm. For example, we might have a Connect
method in our AccessDatabase
class. We need not to be informed how exactly the method Connect
connects to the database. We only have to know that it is used to connect to a database. This is essential in dividing responsibilities in programming, especially in large applications.
Objects group state and behavior, methods represent the behavioral part of the objects.
using System; var c = new Circle(); c.SetRadius(5); Console.WriteLine(c.Area()); class Circle { private int radius; public void SetRadius(int radius) { this.radius = radius; } public double Area() { return this.radius * this.radius * Math.PI; } }
In the code example, we have a Circle class. We define two methods.
private int radius;
We have one member field. It is the radius of the circle. The private
keyword is an access specifier. It tells that the variable is restricted to the outside world. If we want to modify this variable from the outside, we must use the publicly available SetRadius
method. This way we protect our data.
public void SetRadius(int radius) { this.radius = radius; }
This is the SetRadius
method. The this
variable is a special variable which we use to access the member fields from methods. The this.radius
is an instance variable, while the radius is a local variable, valid only inside the SetRadius
method.
var c = new Circle(); c.SetRadius(5);
We create an instance of the Circle
class and set its radius by calling the SetRadius
method on the object of the circle. We use the dot operator to call the method.
public double Area() { return this.radius * this.radius * Math.PI; }
The Area
method returns the area of a circle. The Math.PI
is a built-in constant.
$ dotnet run 78.5398163397448
C# access modifiers
Access modifiers set the visibility of methods and member fields. C# has four basic access modifiers: public
, protected
, private
and internal
. The public
members can be accessed from anywhere. The protected
members can be accessed only within the class itself and by inherited and parent classes. The private
members are limited to the containing type, e.g. only within its class or interface. The internal
members may be accessed from within the same assembly (exe or DLL).
There are also two combinations of modifiers: protected internal
and private protected
. The protected internal
type or member can be accessed by any code in the assembly in which it is declared, or from within a derived class in another assembly. The private protected
type or member can be accessed only within its declaring assembly, by code in the same class or in a type that is derived from that class.
Access modifiers protect data against accidental modifications. They make the programs more robust.
Class | Current assembly | Derived types | Derived types in current assembly | Entire program | |
---|---|---|---|---|---|
public | + | + | + | + | + |
protected | + | o | + | + | o |
internal | + | + | o | o | o |
private | + | o | o | o | o |
protected internal | + | + | + | + | o |
private protected | + | o | o | + | o |
The above table summarizes C# access modifiers (+ is accessible, o is not accessible).
using System; var p = new Person(); p.name = "Jane"; p.SetAge(17); Console.WriteLine($"{p.name} is {p.GetAge()} years old"); class Person { public string name; private int age; public int GetAge() { return this.age; } public void SetAge(int age) { this.age = age; } }
In the above program, we have two member fields. One is declared public, the other private.
public int GetAge() { return this.age; }
If a member field is private
, the only way to access it is via methods. If we want to modify an attribute outside the class, the method must be declared public
. This is an important aspect of data protection.
public void SetAge(int age) { this.age = age; }
The SetAge()
method enables us to change the private
age variable from outside of the class definition.
var p = new Person(); p.name = "Jane";
We create a new instance of the Person
class. Because the name attribute is public
, we can access it directly. However, this is not recommended.
p.SetAge(17);
The SetAge
method modifies the age member field. It cannot be accessed or modified directly because it is declared private
.
Console.WriteLine($"{p.name} is {p.GetAge()} years old");
Finally, we access both members to build a string.
$ dotnet run Jane is 17 years old
Member fields with private
access modifiers are not inherited by derived classes.
using System; var derived = new Derived(); derived.info(); class Base { public string name = "Base"; protected int id = 5323; private bool isDefined = true; } class Derived : Base { public void info() { Console.WriteLine("This is Derived class"); Console.WriteLine("Members inherited"); Console.WriteLine(this.name); Console.WriteLine(this.id); // Console.WriteLine(this.isDefined); } }
In the preceding program, we have a Derived
class which inherits from the Base
class. The Base
class has three member fields. All with different access modifiers. The isDefined member is not inherited. The private
modifier prevents this.
class Derived : Base
The class Derived
inherits from the Base
class. To inherit from another class, we use the colon (:) operator.
Console.WriteLine(this.name); Console.WriteLine(this.id); // Console.WriteLine(this.isDefined);
The public
and the protected
members are inherited by the Derived
class. They can be accessed. The private
member is not inherited. The line accessing the member field is commented. If we uncommented the line, the code would not compile.
$ dotnet run ... warning CS0414: The field 'Base.isDefined' is assigned but its value is never used ... This is Derived class Members inherited Base 5323
C# constructor
A constructor is a special kind of a method. It is automatically called when the object is created. Constructors do not return values. The purpose of the constructor is to initiate the state of an object. Constructors have the same name as the class. The constructors are methods, so they can be overloaded too.
Constructors cannot be inherited. They are called in the order of inheritance. If we do not write any constructor for a class, C# provides an implicit default constructor. If we provide any kind of a constructor, then a default is not supplied.
using System; new Being(); new Being("Tom"); class Being { public Being() { Console.WriteLine("Being is created"); } public Being(string being) { Console.WriteLine($"Being {being} is created"); } }
We have a Being
class. This class has two constructors. The first one does not take parameters; the second one takes one parameter.
public Being(string being) { Console.WriteLine($"Being {being} is created"); }
This constructor takes one string parameter.
new Being();
An instance of the Being
class is created. This time the constructor without a parameter is called upon object creation.
$ dotnet run Being is created Being Tom is created
In the next example, we initiate data members of the class. Initiation of variables is a typical job for constructors.
using System; var name = "Lenka"; var born = new DateTime(1990, 3, 5); var friend = new MyFriend(name, born); friend.Info(); class MyFriend { private DateTime born; private string name; public MyFriend(string name, DateTime born) { this.name = name; this.born = born; } public void Info() { Console.WriteLine("{0} was born on {1}", this.name, this.born.ToShortDateString()); } }
We have a MyFriend
class with data members and methods.
private DateTime born; private string name;
We have two private variables in the class definition.
public MyFriend(string name, DateTime born) { this.name = name; this.born = born; }
In the constructor, we initiate the two data members. The this
variable is a handler used to reference the object variables.
var friend = new MyFriend(name, born); friend.Info();
We create a MyFriend
object with two arguments. Then we call the Info
method of the object.
$ dotnet run Lenka was born on 3/5/1990
C# constructor chaining
Constructor chaining is the ability of a class to call another constructor from a constructor. To call another constructor from the same class, we use the this
keyword.
using System; new Circle(5); new Circle(); class Circle { public Circle(int radius) { Console.WriteLine($"Circle, r={radius} is created"); } public Circle() : this(1) { } }
We have a Circle
class. The class has two constructors. One that takes one parameter and one that does not take any parameters.
public Circle(int radius) { Console.WriteLine("Circle, r={0} is created", radius); }
This constructor takes one parameter — the radius
.
public Circle() : this(1) { }
This is the constructor without a parameter. It simply calls the other constructor and gives it a default radius of 1.
$ dotnet run Circle, r=5 is created Circle, r=1 is created
C# ToString method
Each object has a ToString
method. It returns a human-readable representation of an object. The default implementation returns the fully qualified name of the type of the Object
. Note that when we call the Console.WriteLine
method with an object as a parameter, the ToString
is being called.
using System; var b = new Being(); var o = new Object(); Console.WriteLine(o.ToString()); Console.WriteLine(b.ToString()); Console.WriteLine(b); class Being { public override string ToString() { return "This is Being class"; } }
We have a Being
class in which we override the default implementation of the ToString
method.
public override string ToString() { return "This is Being class"; }
Each class created inherits from the base object
. The ToString()
method belongs to this object class. We use the override
keyword to inform that we are overriding a method.
var b = new Being(); var o = new Object();
We create one custom defined object and one built-in object.
Console.WriteLine(o.ToString()); Console.WriteLine(b.ToString());
We call the ToString
method on these two objects.
Console.WriteLine(b);
As we have specified earlier, placing an object as a parameter to the Console.WriteLine
will call its ToString
method. This time, we have called the method implicitly.
$ dotnet run System.Object This is Being class This is Being class
C# object initializers
Object initializers let us assign values to any accessible fields or properties of an object at creation time without having to invoke a constructor. The properties or fields are assigned inside the {}
brackets. Also, we can specify arguments for a constructor or omit the arguments.
using System; var u = new User { Name = "John Doe", Occupation = "gardener" }; Console.WriteLine(u); class User { public User() {} public string Name { set; get; } public string Occupation { set; get; } public override string ToString() { return $"{Name} is a {Occupation}"; } }
In the example, we create a new user with the object initializer syntax.
public User() {}
We define an empty constructor.
public string Name { set; get; } public string Occupation { set; get; }
We have two properties: Name
and Occupation
.
var u = new User { Name = "John Doe", Occupation = "gardener" };
We assign the values to the properties in the {}
brackets.
$ dotnet run John Doe is a gardener
C# class constants
C# enables to create class constants. These constants do not belong to a concrete object. They belong to the class. By convention, constants are written in uppercase letters.
using System; Console.WriteLine(Math.PI); class Math { public const double PI = 3.14159265359; }
We have a Math
class with a PI
constant.
public const double PI = 3.14159265359;
The const
keyword is used to define a constant. The public
keyword makes it accessible outside the body of the class.
$ dotnet run 3.14159265359
C# inheritance
The inheritance is a way to form new classes using classes that have already been defined. The newly formed classes are called derived classes, the classes that we derive from are called base classes. Important benefits of inheritance are code reuse and reduction of complexity of a program. The derived classes (descendants) override or extend the functionality of the base classes (ancestors).
using System; namespace Inheritance { class Being { public Being() { Console.WriteLine("Being is created"); } } class Human : Being { public Human() { Console.WriteLine("Human is created"); } } class Program { static void Main(string[] args) { new Human(); } } }
In this program, we have two classes. A base Being
class and a derived Human
class. The derived class inherits from the base class.
class Human : Being
In C#, we use the colon (:) operator to create inheritance relations.
new Human();
We instantiate the derived Human
class.
$ dotnet run Being is created Human is created
We can see that both constructors were called. First, the constructor of the base class is called, then the constructor of the derived class.
A more complex example follows.
using System; namespace Inheritance2 { class Being { static int count = 0; public Being() { count++; Console.WriteLine("Being is created"); } public void GetCount() { Console.WriteLine("There are {0} Beings", count); } } class Human : Being { public Human() { Console.WriteLine("Human is created"); } } class Animal : Being { public Animal() { Console.WriteLine("Animal is created"); } } class Dog : Animal { public Dog() { Console.WriteLine("Dog is created"); } } class Program { static void Main(string[] args) { new Human(); var dog = new Dog(); dog.GetCount(); } } }
We have four classes. The inheritance hierarchy is more complicated. The Human
and the Animal
classes inherit from the Being
class. The Dog class inherits directly from the Animal
class and indirectly from the Being
class. We also introduce a concept of a static
variable.
static int count = 0;
We define a static
variable. Static members are members that are shared by all instances of a class.
Being() { count++; Console.WriteLine("Being is created"); }
Each time the Being
class is instantiated, we increase the count variable by one. This way we keep track of the number of instances created.
class Animal : Being ... class Dog : Animal ...
The Animal
inherits from the Being
and the Dog
inherits from the Animal
. Indirectly, the Dog
inherits from the Being
as well.
new Human(); var dog = new Dog(); dog.GetCount();
We create instances from the Human
and from the Dog
classes. We call the GetCount()
method of the Dog object.
$ dotnet run Being is created Human is created Being is created Animal is created Dog is created There are 2 Beings
The Human
calls two constructors. The Dog
calls three constructors. There are two Beings instantiated.
We use the base
keyword to call the parent's constructor explicitly.
using System; namespace Shapes { class Shape { protected int x; protected int y; public Shape() { Console.WriteLine("Shape is created"); } public Shape(int x, int y) { this.x = x; this.y = y; } } class Circle : Shape { private int r; public Circle(int r, int x, int y) : base(x, y) { this.r = r; } public override string ToString() { return String.Format("Circle, r:{0}, x:{1}, y:{2}", r, x, y); } } class Program { static void Main(string[] args) { var c = new Circle(2, 5, 6); Console.WriteLine(c); } } }
We have two classes: the Shape
class and the Circle
class. The Shape
class is a base class for geometrical shapes. We can put into this class some commonalities of the common shapes, like the x
and y
coordinates.
public Shape() { Console.WriteLine("Shape is created"); } public Shape(int x, int y) { this.x = x; this.y = y; }
The Shape
class has two constructors. The first one is the default constructor. The second one takes two parameters: the x, y coordinates.
public Circle(int r, int x, int y) : base(x, y) { this.r = r; }
This is the constructor for the Circle
class. This constructor initiates the r
member and calls the parent's second constructor, to which it passes the x
, y
coordinates. Have we not called the constructor explicitly with the base
keyword, the default constructor of the Shape
class would be called.
$ dotnet run Circle, r:2, x:5, y:6
C# abstract classes and methods
Abstract classes cannot be instantiated. If a class contains at least one abstract method, it must be declared abstract too. Abstract methods cannot be implemented; they merely declare the methods' signatures. When we inherit from an abstract class, all abstract methods must be implemented by the derived class. Furthermore, these methods must be declared with the same of less restricted visibility.
Unlike Interfaces, abstract classes may have methods with full implementation and may also have defined member fields. So abstract classes may provide a partial implementation. Programmers often put some common functionality into abstract classes. And these abstract classes are later subclassed to provide more specific implementation.
For example, the Qt graphics library has a QAbstractButton
, which is the abstract base class of button widgets, providing functionality common to buttons. Buttons Q3Button
, QCheckBox
, QPushButton
, QRadioButton
, and QToolButton
inherit from this base abstract class.
Formally put, abstract classes are used to enforce a protocol. A protocol is a set of operations which all implementing objects must support.
using System; namespace AbstractClass { abstract class Drawing { protected int x = 0; protected int y = 0; public abstract double Area(); public string GetCoordinates() { return string.Format("x: {0}, y: {1}", this.x, this.y); } } class Circle : Drawing { private int r; public Circle(int x, int y, int r) { this.x = x; this.y = y; this.r = r; } public override double Area() { return this.r * this.r * Math.PI; } public override string ToString() { return string.Format("Circle at x: {0}, y: {1}, radius: {2}", this.x, this.y, this.r); } } class Program { static void Main(string[] args) { var c = new Circle(12, 45, 22); Console.WriteLine(c); Console.WriteLine("Area of circle: {0}", c.Area()); Console.WriteLine(c.GetCoordinates()); } } }
We have an abstract base Drawing
class. The class defines two member fields, defines one method and declares one method. One of the methods is abstract, the other one is fully implemented. The Drawing
class is abstract because we cannot draw it. We can draw a circle, a dot or a square. The Drawing
class has some common functionality to the objects that we can draw.
abstract class Drawing
We use the abstract
keyword to define an abstract class.
public abstract double Area();
An abstract method is also preceded with the abstract
keyword.
class Circle : Drawing
A Circle is a subclass of the Drawing
class. It must implement the abstract Area()
method.
public override double Area() { return this.r * this.r * Math.PI; }
When we implement the Area()
method, we must use the override
keyword. This way we inform the compiler that we override an existing (inherited) method.
$ dotnet run Circle at x: 12, y: 45, radius: 22 Area of circle: 1520.53084433746 x: 12, y: 45
C# partial class
With the partial
keyword, it is possible to split the definition of a class into several parts inside the same namespace. The class can also be defined in multiple files.
Partial classes are used when working with very large code base, which can be split into smaller units. Partial classes are also used with automatic code generators.
using System; namespace PartialClass { partial class Worker { public string DoWork() { return "Doing work"; } } partial class Worker { public string DoPause() { return "Pausing"; } } class Program { static void Main(string[] args) { var worker = new Worker(); Console.WriteLine(worker.DoWork()); Console.WriteLine(worker.DoWork()); Console.WriteLine(worker.DoPause()); } } }
In the example, we have the Worker
class defined in two parts. The parts are joined together by the compiler to form a final class.
$ dotnet run Doing work Doing work Pausing
This was the first part of the description of OOP in C#.