Skip to content
wannacode

Encapsulation

Problem

Minus €500

In the last lesson we put the withdrawal check in a method. But the field is open, and anyone can write to it directly:

Account account = new Account("Maria", 100);
account.Withdraw(500);
account.Balance = -500;
Console.WriteLine(account.Balance);
Output
-500

The method correctly refused to withdraw €500. The next line simply set the balance by hand. The check is there, but you can get around it. We need a way to say "only the account touches the balance".

The idea

private: only the class touches it

Put private in front of the field instead of public:

class Account
{
private string owner;
private double balance;
// ...
}

Now only the code inside the Account class can reach the field. From outside, the line

account.balance = -500;

no longer compiles:

Compile error
error CS0122: 'Account.balance' is inaccessible due to its protection level

private and public are called access modifiers. They say who can see a field, a method or a constructor. The class hides its data and shows it only through its own methods. This is called encapsulation.

In C#, hidden fields are written with a lowercase letter: balance, owner. That's why the constructor says this.balance = balance;: the field and the parameter now have the same name.

More precisely, if you are curious
  • Without a modifier, a field or method in C# is private. That's why we wrote public everywhere until this lesson.
  • A class without a modifier is internal: visible in the whole project (the assembly), but not from other projects. That's why class Product works everywhere in our program.
  • There is also protected: visible in the class and in its subclasses. You'll meet it in the module "Inheritance and polymorphism".

The idea

Reading without touching

From outside, we can't even read the balance now. So the class gives a method that only returns it:

Program.cs
class Account
{
private string owner;
private double balance;
public Account(string owner, double balance)
{
this.owner = owner;
this.balance = balance;
}
public string Owner
{
get { return owner; }
}
public double Balance
{
get { return balance; }
}
public void Deposit(double amount)
{
if (amount > 0)
{
balance += amount;
}
}
public bool Withdraw(double amount)
{
if (amount > 0 && amount <= balance)
{
balance -= amount;
return true;
}
return false;
}
}
class Program
{
static void Main()
{
Account account = new Account("Maria", 100);
account.Withdraw(30);
account.Deposit(50);
Console.WriteLine(account.Owner + ": €" + account.Balance);
}
}
Output
Maria: €120

Balance is a property. From outside, you use it like a field: account.Balance. Inside, it is a method: get returns the value of the hidden balance field. This is the C# getter. The balance changes only through Deposit and Withdraw, and they do the checks.

Withdraw now returns bool: whether the withdrawal went through. This way, whoever withdraws knows if it worked.

Account account = new Account("Maria", 100);
account.Withdraw(500);
account.Deposit(50);
Console.WriteLine(account.Balance);
Step by step, in words
  1. balance is private: the glass means there is no way to the field from outside.
  2. The withdrawal goes through the check in the method: 500 is more than 100. Refused, and the balance stays as it is.
  3. The deposit is checked too: 50 is a positive amount. The method adds it.
  4. The Balance property hands out a copy of the balance. The field itself stays hidden.

Try it

Refused

What will the program print?

Account account = new Account("Maria", 100);
Console.WriteLine(account.Withdraw(150));
account.Deposit(-20);
Console.WriteLine(account.Balance);
Answer

The idea

Setter: a change with a check

Sometimes code outside needs to change a field, but not to just any value. An animal's age can change, but it can't be negative:

class Animal
{
private int age;
public int Age
{
get { return age; }
set
{
if (value >= 0)
{
age = value;
}
}
}
}
Animal cat = new Animal();
cat.Age = 4;
cat.Age = -3;
Console.WriteLine(cat.Age);
Output
4

The property now has a set too: this is the setter. With cat.Age = -3, the code in set runs, and value is the value on the right of =, here -3. The check fails and the age stays 4.

The idea

Which field gets a setter?

Not every field needs a setter, and some don't even need a getter. Think about what code outside should be able to do with the product:

  • the name and the price are set when the product is created and are only read after that: getter only;
  • the quantity in the cart changes, but it's at least 1: a getter and a setter with a check.
class Product
{
private string name;
private double price;
private int quantity;
public Product(string name, double price)
{
this.name = name;
this.price = price;
this.quantity = 1;
}
public string Name
{
get { return name; }
}
public double Price
{
get { return price; }
}
public int Quantity
{
get { return quantity; }
set
{
if (value >= 1)
{
quantity = value;
}
}
}
}
Product coffee = new Product("Coffee", 7.50);
coffee.Quantity = 3;
coffee.Quantity = 0;
Console.WriteLine(coffee.Name + " x" + coffee.Quantity);
Output
Coffee x3

The rule is: start with private for everything and open only what you need. A setter that stores anything without a check brings back the problem from the first card.

The idea

Properties in short

A property that only reads and writes a field without a check can be written on one line. C# makes the hidden field itself:

class Product
{
public string Name { get; set; }
public double Price { get; set; }
public int Quantity { get; set; } = 1;
}
Product coffee = new Product { Name = "Coffee", Price = 7.50 };
Console.WriteLine(coffee.Name + " x" + coffee.Quantity + ": €" + coffee.Price);
Output
Coffee x1: €7.5

{ get; set; } is an auto-implemented property. With = 1, it has a default value. new Product { Name = "Coffee", Price = 7.50 } is an object initializer: it creates the object and sets its properties in one pair of braces.

You can hide just the write. { get; private set; } can be read from anywhere, but changed only in the class. { get; } is set only in the constructor:

class Account
{
public string Owner { get; }
public double Balance { get; private set; }
public Account(string owner, double balance)
{
Owner = owner;
Balance = balance;
}
public void Deposit(double amount)
{
if (amount > 0)
{
Balance += amount;
}
}
}
Account account = new Account("Maria", 100);
account.Deposit(20);
Console.WriteLine(account.Owner + ": €" + account.Balance);
Output
Maria: €120

The line account.Balance = -500; in Main doesn't compile:

Compile error
error CS0272: The property or indexer 'Account.Balance' cannot be used in this context because the set accessor is inaccessible

Try it

What's missing?

The balance should be visible only inside the class. What goes in the blank?
class Account
{
     double balance;
}

Try it

Find the bug

The field is private, and yet the balance becomes -500:

Account account = new Account();
account.Balance = 100;
account.Withdraw(30);
account.Balance = -500;
Console.WriteLine(account.Balance);
Output
-500

Which line in the class breaks the protection?

Pick the line with the bug.

Exercise

Hidden data

Solve the tasks in your editor. All fields are private.

1. Person. Person with a name and an age, a constructor, read-only properties Name and Age and Introduce().

Output
Maria is 34 years old.
Hi, I'm Maria and I'm 34 years old.

2. An account with no minus. Account with an owner and a balance. A deposit accepts only a positive amount. A withdrawal returns whether it went through and never lets the balance go below 0. Code outside can read the balance, but can't write it.

Main
Account account = new Account("Peter", 50);
account.Deposit(25);
account.Deposit(-10);
Console.WriteLine(account.Withdraw(100));
Console.WriteLine(account.Withdraw(40));
Console.WriteLine("Balance: €" + account.Balance);
Output
False
True
Balance: €35

3. Animal. Animal with a species and an age. The species is set in the constructor and is only read after that. The age has a set that refuses a negative number and prints a message. The constructor goes through the same check too.

Main
Animal cat = new Animal("cat", 3);
cat.Age = 4;
cat.Age = -1;
Console.WriteLine(cat.Species + ", " + cat.Age + " years old");
Output
Invalid age: -1
cat, 4 years old

More tasks

  • Grade. A Grade class with a subject name and a grade from 2 to 6. The setter refuses anything outside those limits. Try it with 5, 7 and 1.
  • Product with a discount. Add a method to the Product from the lesson that applies a discount in percent and accepts only 1 to 90%. The price has no setter.

Test

Check yourself

1. The balance field is private. Where can it be changed from?
2. Why does the account have no setter for the balance?
3. In the set of a property, what is value?
4. What does encapsulation mean?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·