Classes and Instances
A class describes a kind of thing; new builds one object from it, and each object holds its own copy of the instance data.
class TypeName { /* members */ } // then: TypeName name = new TypeName();A class is a description of a kind of thing. It lists what that thing stores and what it can do. An object is one actual thing built from that description. The class is the blueprint. The object is the house built from the blueprint. One blueprint, many houses, each with its own furniture.
Classes exist because programs deal in things rather than loose variables. A counter, an invoice, a database connection. Keeping the data next to the code that changes it stops the two drifting apart.
Building one
You write the class once. Then you build objects from it with the new keyword. new Counter() sets aside memory for one Counter and hands you a way to reach it.
Anything declared inside the class without the word static is an instance member. A variable declared there is an instance field. A method declared there is an instance method. Every object gets its own copy of every instance field. Two Counter objects count separately. A method called on one object sees that object's fields and no other object's.
What trips people up
A variable of a class type does not hold the object. It holds a reference, which is the address of the object rather than the object itself. So Counter b = a; copies the address. Now a and b name one single object. Incrementing through b changes what a reads. Making a real copy of an object takes explicit work.
Example
// A class is a blueprint. Each `new` builds one object from it.
Counter clicks = new Counter();
Counter errors = new Counter();
clicks.Increment();
clicks.Increment();
clicks.Increment();
errors.Increment();
// The two objects counted separately.
Console.WriteLine($"clicks: {clicks.Read()}");
Console.WriteLine($"errors: {errors.Read()}");
class Counter
{
// An instance field. Every Counter object gets its own _total.
private int _total;
// An instance method. It changes the object you called it on.
public void Increment()
{
_total = _total + 1;
}
public int Read()
{
return _total;
}
}
// Output:
// clicks: 3
// errors: 1When to use it
- Model one order, one customer or one shopping basket as a thing with its own data instead of five parallel variables.
- Build many independent counters, caches or parsers from one definition without copying code.
- Track down a bug where a value changes unexpectedly because two variables share one object.
More examples
Two names, one object
This is the single most common surprise about classes. Reach for it whenever a value changes and you cannot see where: check whether something else is holding the same object. If you want an independent object you must build a second one with new and copy the data across yourself.
Counter a = new Counter();
// b is given the address of the object a refers to.
// No second Counter is built here.
Counter b = a;
a.Increment();
b.Increment();
// Both lines read the same object, so both say 2.
Console.WriteLine($"a reads {a.Read()}");
Console.WriteLine($"b reads {b.Read()}");
class Counter
{
private int _total;
public void Increment()
{
_total = _total + 1;
}
public int Read()
{
return _total;
}
}
// Output:
// a reads 2
// b reads 2The new() shorthand
The short form arrived in C# 9 and is normal in current code, so you need to recognise it. It only works where the compiler already knows the type: a variable with a written type, a field, a method argument, a return value. Writing var shorthand = new(); does not compile, because then nothing says what to build.
// Longhand: the type is written twice.
Basket longhand = new Basket();
// Shorthand: new() with no type name. The compiler takes the type
// from the left-hand side. These two lines mean exactly the same thing.
Basket shorthand = new();
longhand.Add(3);
shorthand.Add(4);
Console.WriteLine($"{longhand.Count()} and {shorthand.Count()}");
class Basket
{
private int _items;
public void Add(int count)
{
_items = _items + count;
}
public int Count()
{
return _items;
}
}
// Output:
// 3 and 4A class in its own file
Real projects put one class in one file named after it, rather than stacking classes under top-level statements. The namespace groups related classes so two projects can both have a Bin without clashing; code elsewhere writes using Shop.Inventory; to reach it. The file-scoped form, namespace Shop.Inventory; with a semicolon, replaces wrapping the whole file in braces.
// Bin.cs
namespace Shop.Inventory;
// internal, which is also the default for a top-level type with no
// modifier: this class is visible inside this project only.
internal class Bin
{
private int _units;
public void Receive(int units)
{
_units = _units + units;
}
public int Units()
{
return _units;
}
}
Discussion