static: what all objects share
Problem
Every product is #1
The shop wants every new product to get the next number: 1, 2, 3… You put a counter nextId in the class. The constructor gives the product the current number and increases the counter:
class Product{ private int nextId = 1; private int id; private string name;
public Product(string name) { this.name = name; id = nextId; nextId++; }
public override string ToString() { return "#" + id + " " + name; }}
Product bread = new Product("Bread");Product milk = new Product("Milk");Product coffee = new Product("Coffee");Console.WriteLine(bread);Console.WriteLine(milk);Console.WriteLine(coffee);#1 Bread#1 Milk#1 CoffeeThey are all #1. The reason is that nextId is a field of the object, just like the name. Every new product gets its own counter, which starts at 1. The bread's counter never reaches the milk.
The idea
A field of the class, not of the object
The counter is not part of one product. It is shared by all products. Add static:
private static int nextId = 1;A static field is a field of the class. There is one, no matter how many objects you create, and all the objects share it:
class Product{ private static int nextId = 1; private int id; private string name;
public Product(string name) { this.name = name; id = nextId; nextId++; }
public override string ToString() { return "#" + id + " " + name; }}
class Program{ static void Main() { Product bread = new Product("Bread"); Product milk = new Product("Milk"); Product coffee = new Product("Coffee"); Console.WriteLine(bread); Console.WriteLine(milk); Console.WriteLine(coffee); }}#1 Bread#2 Milk#3 CoffeeThe bread takes 1 and makes the counter 2. The milk sees the same counter, takes 2 and makes it 3.
private static int nextId = 1;
public Product(string name){ this.name = name; id = nextId; nextId++;}
Product bread = new Product("Bread");Product milk = new Product("Milk");Product coffee = new Product("Coffee");Step by step, in words
- nextId is static: one field in the class, not one in each object.
- new Product("Bread"): a new object. The constructor stores the name.
- id = nextId: the object takes the current number, 1.
- nextId++ changes the shared counter: now it is 2.
- Milk: the same counter. It takes 2 and makes it 3.
- Coffee: it takes 3 and makes it 4.
- There is one counter for all of them, so the numbers go in order.
If the field is public static, you reach it from outside through the class name (Product.nextId), not through an object.
Try it
Shared and own
What will the program print?
class Order{ public static int Count = 0; public int Items = 0;
public Order() { Count++; Items++; }}
Order a = new Order();Order b = new Order();Order c = new Order();Console.WriteLine(Order.Count + " " + c.Items);Count is static: one counter for all orders, increased three times. Items is a field of the object: each order has its own, increased once.The idea
A static method: work without an object
The same goes for methods. A static method is a method of the class. You call it through the class name, without new:
class Calculator{ public static int Add(int a, int b) { return a + b; }
public static double Average(int[] values) { int sum = 0; foreach (int v in values) { sum += v; } return (double)sum / values.Length; }}
Console.WriteLine(Calculator.Add(10, 20));int[] grades = { 5, 6, 4, 6 };Console.WriteLine(Calculator.Average(grades));Console.WriteLine(Math.Max(3, 8));305.258Calculator.Add does not need an object: everything it needs comes as parameters. The methods of Math are like that too: Math.Max, Math.Sqrt. Nobody writes new Math().
Now it is clear why Main is static. When the program starts, there are no objects yet. The runtime calls Main directly through the class. That is also why the methods from the "Methods" module, which we called from Main, were static.
More precisely, if you are curious
The reverse also works: in a static method, you can create an object and call its methods. That is exactly what we have been doing in Main since the start of the module.
Try it
Find the bug
Pick the line with the bug.
Label() reads name, and name belongs to a specific product. A static method has no object, so it does not know whose name to take:error CS0120: An object reference is required for the non-static field, method, or property 'Product.name'static: the label belongs to the product, so it is an instance method.The idea
Constants
VAT is 20% for all products, and it does not change while the program runs. A value like that is a constant:
class Prices{ public const double VatRate = 0.20;
public static double WithVat(double price) { return price * (1 + VatRate); }}
Console.WriteLine(Prices.WithVat(50));Console.WriteLine("VAT: " + Prices.VatRate * 100 + "%");60VAT: 20%const means that the value is known at compile time and cannot be changed. A const always belongs to the class, so you do not write static. The names look like property names: VatRate.
If someone tries to change it,
Prices.VatRate = 0.25;the program does not compile:
error CS0131: The left-hand side of an assignment must be a variable, property or indexerYou win twice: the number 0.20 is written in one place with a clear name, and nobody can change it by mistake.
More precisely, if you are curious
Sometimes the value is different for each object, but it must not change after the object is created. The product number is like that:
private static int nextId = 1;private readonly int id;
public Product(string name){ this.name = name; id = nextId; nextId++;}A readonly field is set once, in the constructor, and does not change after that. There is also static readonly: like const, but the value is computed when the program runs, not at compile time. You use it for values that are not just a number or text.
Try it
What is missing?
class Calendar
{
public int Months = 12;
}const: the value is a constant of the class. You do not write static before const.The idea
When static and when not
Ask yourself: "Does this belong to a specific object?"
- Yes (the balance of the account, the name of the product, withdrawing from an account): a field or a method of the object, without
static. - No (the number of all accounts, VAT, the sum of two numbers):
static.
A common mistake is to make everything static, because Main is static and the compiler stops complaining. Then all the "objects" share the same data, and you are back at the problem with the parallel arrays from the first lesson.
Exercise
What all objects share
Solve the tasks in your editor.
1. Orders. An Order class with a customer and an amount. Every new order gets the next number from a static counter. A read-only static property Count tells how many orders have been created.
Order[] orders ={ new Order("Maria", 45.5), new Order("Peter", 120), new Order("Elena", 18.9)};foreach (Order o in orders){ Console.WriteLine(o);}Console.WriteLine("Orders: " + Order.Count);Order #1: Maria, €45.5Order #2: Peter, €120Order #3: Elena, €18.9Orders: 32. Calculator. A Calculator class with static methods for adding, subtracting and multiplying whole numbers, and for dividing decimal numbers.
Console.WriteLine(Calculator.Add(10, 20));Console.WriteLine(Calculator.Subtract(10, 15));Console.WriteLine(Calculator.Multiply(6, 7));Console.WriteLine(Calculator.Divide(5, 2));30-5422.53. Exchange. A Converter class with constants for the rate: €1 = 1.08 US dollars and €1 = 0.85 British pounds (the rates are examples). Static methods convert an amount in euros to dollars and to pounds. For €20:
20 EUR = 21.6 USD20 EUR = 17 GBPMore tasks
- Numbered accounts. Add to the
Accountfrom the "Encapsulation" lesson an account number that comes from a static counter and areadonlyfield for the number. - Exchange both ways. Add methods to
Converterthat convert from dollars and from pounds to euros. Convert €20 to dollars and back to euros. Do you get exactly 20? - A limit. The shop accepts at most 3 products in a cart. Add a constant for that, and make the add method refuse the fourth one.
In short
The module in short
- A class is your own type, and an object is a specific instance of it, created with
new. The variable points to the object. Your own type - The constructor makes sure the object is created whole.
thisis the current object. Constructors and this - Instance methods work with the object's fields.
ToString()says what it looks like as text. Behavior privatehides the fields. From outside, you reach them only through methods and properties that check. Encapsulationstaticis what the class shares: counters, helper methods and constants.
Data and rules in one place that nobody can get around: that is object-oriented programming. In the next module, classes will start to share code with each other.
Test