Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

C# & .NET (C# 14, .NET 10 LTS)  Module 6 – Classes, objects and encapsulation

Static members, static classes and shared state

Decide when a C# member should be static, see exactly when a static constructor runs, and stop shared static state from tying your tests to their order.

  • Intermediate
  • 20 minutes
  • Examples run with .NET 10.0.401
  • By MySmartCoPilot
Free core, Pro depth: 7 of 20 minutes free
Free: A static member belongs to the type, not to any object · A static constructor runs once, just before the type is first used · Shared mutable state ties tests to their order. With a pass: 3 in-depth parts and 3 interview answers, about 13 minutes.

What you will learn

  • Decide whether a member belongs to the type or to each object
  • Predict when static field initializers and a static constructor run
  • Replace a static counter with an object passed in, so that tests pass in any order
  • Diagnose a type whose static constructor failed, and design initialisation that can recover
  • Recognise what shared static state costs under threads, and protect a shared counter

Before you start

What you’ll learn · Before you start

What you will learn

  • Decide whether a member belongs to the type or to each object
  • Predict when static field initializers and a static constructor run
  • Replace a static counter with an object passed in, so that tests pass in any order
  • Diagnose a type whose static constructor failed, and design initialisation that can recover
  • Recognise what shared static state costs under threads, and protect a shared counter

Before you start

On this page

A checkout that numbers its orders through a static counter passed its first test with ORD-1001 and failed the second, whose first order came out as ORD-1002. Run the two tests the other way round and the other one fails. A static field belongs to the whole program, so every test, every thread and every request shares it.

You will decide which members should be static, see exactly when a static constructor runs, and replace shared static state with an object that each test or service is given.

A static member belongs to the type, not to any object

tickets.cs creates three tickets. Each has its own passenger and number, but the count of tickets issued is a static member: A field, property, method or event declared static. It belongs to the type rather than to any object, exists once in the program, and is reached through the type's name, as in Ticket.Issued.: it belongs to the type Ticket itself, exists once however many objects there are, and is reached through the type’s name, Ticket.Issued:

tickets.cs: one static counter, three tickets C# · tickets/tickets.cs
// A static field belongs to the type: one value that every Ticket shares. An instance field belongs to each ticket.
var first = new Ticket("Asha Rao");
var second = new Ticket("Ravi Iyer");
var third = new Ticket("Meena Das");

Console.WriteLine($"{first.Passenger} has ticket {first.Number}");
Console.WriteLine($"{third.Passenger} has ticket {third.Number}");
Console.WriteLine($"tickets issued: {Ticket.Issued}");
Console.WriteLine($"fare for 3 adults and 2 children: {Fares.Total(adults: 3, children: 2)} paise");

class Ticket
{
    // One counter for the whole program, shared by every Ticket object.
    private static int s_issued;

    public Ticket(string passenger)
    {
        s_issued++;
        Passenger = passenger;
        Number = 1000 + s_issued;
    }

    // Instance members: each ticket has its own.
    public string Passenger { get; }

    public int Number { get; }

    // A static property: read through the type, never through a ticket.
    public static int Issued => s_issued;
}

// A static class holds only static members and can never be created with new.
static class Fares
{
    public const long AdultPaise = 45_000;

    public static long Total(int adults, int children) => (adults * AdultPaise) + (children * AdultPaise / 2);
}

Output

Asha Rao has ticket 1001
Meena Das has ticket 1003
tickets issued: 3
fare for 3 adults and 2 children: 180000 paise

Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run tickets.cs

Step through the three constructors and watch the one field they all change:

Interactive · step by step One static field, three objects Step through tickets.cs. The type and its static members stand on the left, the objects on the right.
One static field, three objects, step 5 of 5. Showing Ticket, the type (s_issued = 3), The first ticket, The second ticket, The third ticket, Fares, a static class. In focus: Fares, a static class.Ticket, the types_issued = 3The first ticketAsha Rao, 1001The second ticketRavi Iyer, 1002The third ticketMeena Das, 1003Fares, a static classno objects at all

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. Before any ticket. The static field s_issued belongs to the type Ticket, not to any ticket. It exists once in the program, and it starts at 0 before the first ticket is created.
  2. The first ticket. new Ticket("Asha Rao") runs the constructor, which adds 1 to the shared s_issued and gives this ticket its own Number, 1000 + 1 = 1001.
  3. Two more. The next two tickets add to the same field, so they get 1002 and 1003. Each object keeps its own Passenger and Number; only s_issued is shared.
  4. Read through the type. Ticket.Issued is a static property, read through the type name. It returned 3: one count for the whole program, whichever ticket you would ask.
  5. A static class. Fares.Total(3, 2) needs no object, because Fares is a static class: it holds only static members and can never be created with new. It returned 180000 paise.

A static method has no object, so it cannot touch instance fields unless an object is passed to it. That is the test for making a member static: Fares.Total needs only its parameters and a constant, while Number describes one ticket. Fares is a static class: A class declared static: it holds only static members, cannot be created with new and cannot be inherited from. Math and Console are static classes.: it holds only static members and is never created with new. Static classes such as Math and Console group helpers that need no object at all. A const such as AdultPaise is static by nature; C# allows no static const.

Making a method static because it is shorter to call

A method that reads one object’s data can be static only if that object is passed in. Moving the data into static fields to make the method static turns every object’s state into one shared value.

Checkpoint

Check: Static members and static classes

  1. Question 1 of 2 Which member of tickets.cs could be declared static without any other change?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Fares.Total(int adults, int children), which uses only its parameters and a constant

    Make a member static when it needs only its parameters and static data. Anything that describes one object stays an instance member.

  2. Question 2 of 2 After the three tickets of tickets.cs, the program creates a fourth with new Ticket("Arjun Nair"). What Number does that ticket get?

    Type a number.

    A hint

    The constructor adds 1 to s_issued, which is shared by every ticket created so far.

    Show the answer: Question 2 of 2

    Answer: 1004

    s_issued was 3 after three tickets; the fourth constructor makes it 4 and sets Number to 1000 + 4 = 1004.

A static constructor runs once, just before the type is first used

Static fields need starting values too, and some take more than an expression. A static constructor: A parameterless constructor marked static, such as static Tax() { }. The runtime runs it at most once, just before the type is first used, to initialise the type's static fields., static Tax() { … }, has no parameters and no access modifier, and nobody calls it: the runtime runs it at most once, before the first instance is created or any static member is used. first-use.cs numbers each line it prints:

first-use.cs: when Tax is initialised C# · first-use/first-use.cs
// A static constructor runs once, just before its type is first used. Watch the numbered lines.
Console.WriteLine("1. the program starts");
Console.WriteLine("2. Tax has not been used yet");
Console.WriteLine($"5. Tax.Percent is {Tax.Percent}");
Console.WriteLine($"6. tax on 1000 rupees is {Tax.On(1000m)}, and nothing ran again");

static class Tax
{
    public static readonly int Percent = Show("3. the static field initializer runs", 18);

    static Tax()
    {
        Console.WriteLine("4. the static constructor runs");
    }

    public static decimal On(decimal amount) => amount * Percent / 100;

    private static int Show(string line, int value)
    {
        Console.WriteLine(line);
        return value;
    }
}

Output

1. the program starts
2. Tax has not been used yet
3. the static field initializer runs
4. the static constructor runs
5. Tax.Percent is 18
6. tax on 1000 rupees is 180, and nothing ran again

Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run first-use.cs

Lines 1 and 2 printed before any code of Tax ran. Reading Tax.Percent for line 5 triggered the static field initializer, line 3, and then the static constructor, line 4; line 6 used Tax again and nothing ran:

Interactive · stage by stage When Tax is initialised Step through the numbered lines of first-use.cs and watch when the type's own code runs.
When Tax is initialised, step 6 of 6. Showing The program starts, First use of Tax, Static field initializer (3. Percent = 18), The static constructor (4. static Tax() runs), The read finishes (5. Tax.Percent is 18), Every later use (6. nothing runs again). In focus: Every later use.The program startslines 1 and 2First use of Taxreading Tax.PercentStatic field initializer3. Percent = 18The static constructor4. static Tax() runsThe read finishes5. Tax.Percent is 18Every later use6. nothing runs again

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Not used yet. Lines 1 and 2 print before anything touches Tax, and none of its code has run: a type is initialised when it is first used, not when the program starts.
  2. The first use. Building line 5 reads Tax.Percent, a static member of Tax. That first use makes the runtime initialise the type before the read can finish.
  3. Initializers first. The static field initializers run first, in the order they are written: line 3 is Percent = 18.
  4. Then the constructor. The static constructor runs right after the initializers and before any other use of the type: line 4.
  5. The value. Only now does the read return 18, and line 5 prints "Tax.Percent is 18".
  6. Never again. Line 6 calls Tax.On(1000m), which gave 180, and nothing printed before it: the runtime runs a static constructor at most once.

Microsoft’s guide adds two rules: static field initializers run in the order they are written, immediately before the static constructor, as lines 3 and 4 show, and the runtime runs the static constructor inside a lock, so threads that use the type at the same moment still see it run once.

Checkpoint

Check: When static initialisation runs

  1. Question 1 of 2 Suppose line 2 of first-use.cs printed Tax.On(100m) instead of plain text. When would "the static constructor runs" be printed?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Before line 2 is printed, because building line 2 is now the first use of Tax

    The static constructor runs once, before the first instance is created or the first static member is used, whatever that member is.

  2. Question 2 of 2 How many times does the runtime run the static constructor of a type in one process?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: At most once

    A static constructor initialises the type once. If the type is never used, it never runs at all.

Shared mutable state ties tests to their order

A static field that changes, such as a counter, is shared state of the riskiest kind: whatever one piece of code leaves in it, the next one finds. orders.cs runs two tests of a checkout twice, in both orders, first with a static counter and then with a counter object that each test creates:

orders.cs: two tests, two orders, two designs C# · orders/orders.cs
// Two tests of a checkout that numbers its orders, run in both orders: first with a static counter, then with a
// counter object that each test creates for itself.
Console.WriteLine("static counter");
Run("first-order test, then two-orders test", FirstOrderStatic, TwoOrdersStatic);
StaticOrderNumbers.ResetForTests();
Run("two-orders test, then first-order test", TwoOrdersStatic, FirstOrderStatic);

Console.WriteLine("counter object passed in");
Run("first-order test, then two-orders test", FirstOrder, TwoOrders);
Run("two-orders test, then first-order test", TwoOrders, FirstOrder);

static void Run(string label, params Func<string>[] tests)
{
    Console.WriteLine($"  {label}");
    foreach (var test in tests)
    {
        Console.WriteLine($"    {test()}");
    }
}

static string FirstOrderStatic() => FirstOrderIs1001(new StaticCheckout().PlaceOrder);

static string TwoOrdersStatic() => TwoOrdersAre1001And1002(new StaticCheckout().PlaceOrder);

// Each test creates the counter its checkout uses.
static string FirstOrder() => FirstOrderIs1001(new Checkout(new OrderNumbers(1001)).PlaceOrder);

static string TwoOrders() => TwoOrdersAre1001And1002(new Checkout(new OrderNumbers(1001)).PlaceOrder);

static string FirstOrderIs1001(Func<string> placeOrder)
{
    string got = placeOrder();
    return got == "ORD-1001" ? "first order is ORD-1001: pass" : $"first order is ORD-1001: FAIL, got {got}";
}

static string TwoOrdersAre1001And1002(Func<string> placeOrder)
{
    string got = $"{placeOrder()} and {placeOrder()}";
    string name = "two orders are ORD-1001 and ORD-1002";
    return got == "ORD-1001 and ORD-1002" ? $"{name}: pass" : $"{name}: FAIL, got {got}";
}

// The static design: one counter for the whole process.
static class StaticOrderNumbers
{
    private static int s_next = 1001;

    public static int Next() => s_next++;

    public static void ResetForTests() => s_next = 1001;
}

class StaticCheckout
{
    public string PlaceOrder() => $"ORD-{StaticOrderNumbers.Next()}";
}

// The instance design: whoever creates a checkout decides which counter it uses.
class OrderNumbers
{
    private int _next;

    public OrderNumbers(int first) => _next = first;

    public int Next() => _next++;
}

class Checkout
{
    private readonly OrderNumbers _numbers;

    public Checkout(OrderNumbers numbers) => _numbers = numbers;

    public string PlaceOrder() => $"ORD-{_numbers.Next()}";
}

Output

static counter
  first-order test, then two-orders test
    first order is ORD-1001: pass
    two orders are ORD-1001 and ORD-1002: FAIL, got ORD-1002 and ORD-1003
  two-orders test, then first-order test
    two orders are ORD-1001 and ORD-1002: pass
    first order is ORD-1001: FAIL, got ORD-1003
counter object passed in
  first-order test, then two-orders test
    first order is ORD-1001: pass
    two orders are ORD-1001 and ORD-1002: pass
  two-orders test, then first-order test
    two orders are ORD-1001 and ORD-1002: pass
    first order is ORD-1001: pass

Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run orders.cs

With the static counter each test passed only when it ran first, because the first test left the counter where the second one did not expect it:

Interactive · message by message Two tests and one static counter Step through the two tests in the first order. The counter keeps its value between them, because it belongs to the process.
Two tests and one static counter, step 4 of 4. Messages so far: First-order test to The static counter: Next(); The static counter to First-order test: 1001; Two-orders test to The static counter: Next(), Next(); The static counter to Two-orders test: 1002, 1003.First-order testexpects ORD-1001The static counters_next = 1004Two-orders testexpects 1001, 1002Next()1001Next(), Next()1002, 1003fails in this order

The drawing shows the whole scene; its 4 steps are listed below.

Every step as text
  1. The first test. The first-order test places one order. The checkout asks the static counter, which hands out 1001 and moves on to 1002.
  2. It passes. ORD-1001 is what the test expected, so it passes. It leaves nothing behind but the counter, which now says 1002.
  3. The second test. The two-orders test places two orders. The counter has no idea that a new test started: it hands out 1002 and 1003.
  4. It fails. The test expected ORD-1001 and ORD-1002 and got ORD-1002 and ORD-1003. Run the two tests the other way round and the other one fails, as orders.cs printed.

ResetForTests was needed just to run the second order, and it cannot help when tests run in parallel, which is what xUnit.net does by default with tests in different test classes. Microsoft’s unit-testing guidance asks for isolated, repeatable tests. The counter object meets both: Checkout receives an OrderNumbers through its constructor, each test creates its own, and all four runs passed. A test can give a checkout a fresh counter, and an application can give every checkout the same one.

Key idea

Keep changing state in objects and pass them to the code that needs them: a static field is one value for the whole process, shared by every test, thread and request that touches it.

Checkpoint

Check: Shared mutable state and tests

  1. Question 1 of 2 With the static counter, the first-order test ran first and passed. What did the two-orders test get?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: ORD-1002 and ORD-1003

    Each test passed only when it ran first, because each left the shared counter where the other did not expect it.

  2. Question 2 of 2 Why did all four runs pass once each test created a Checkout with its own new OrderNumbers(1001)?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Each checkout read a counter that only its own test had touched, so nothing carried over between tests

    Passing the counter in lets each test choose its own, and lets an application share one on purpose.

A static constructor that fails, and initialisation that can recover Pro: opens with a Pro, Premium or Ultimate pass

When a static constructor throws, the type it belongs to is broken for the rest of the process. The runs below reproduce that with a configuration value that has a typing mistake, then compare two ways of initialising lazily.

In this part:

  • TypeInitializationException for good: a bad setting, reproduced
  • Lazy<T> caches the failure too, unless it is PublicationOnly

1 runnable example (C#) · about 4 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Three surprises of static state: field order, generic copies and readonly lists Pro: opens with a Pro, Premium or Ultimate pass

Static fields follow rules that instance fields never meet: one order of initialization for the whole type, one copy for each closed generic type, and a readonly that protects less than it seems. One run shows all three.

In this part:

  • Static field initializers run top to bottom: a price with tax of 0.00, measured
  • Each closed generic type has its own statics, and readonly does not freeze a list

1 runnable example (C#) · about 4 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Shared statics under threads, measured, and the designs that test well Pro: opens with a Pro, Premium or Ultimate pass

Tests that share a static field interfere one after another; threads that share one interfere at the same moment. The run below takes 800,000 numbers from one static counter on eight threads, then sends eight threads at a type that has not been initialised yet.

In this part:

  • Eight threads, one static counter: lost updates counted
  • Where static state is safe, and where to put the rest

1 runnable example (C#) · about 4 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Practise: an id generator that each service is given

The old IdGenerator of idgen.cs was a static class with one counter that OrderService and InvoiceService both called. Write it as a class whose first number is passed to its constructor, and give each service the generator it should use. The classes you start from only throw, so all 6 checks fail:

idgen.cs: six checks of IdGenerator and two services C# · idgen/idgen.cs
// Practice: the old IdGenerator was a static class with one static counter that OrderService and InvoiceService both
// called, so tests depended on each other. Write it as a class that each service is given, then run this file until
// all six checks pass. Numbers start at the value passed in, which must be 1 or more.
(string Check, Func<string> Run, string Expected)[] checks =
[
    ("the first id", () => new IdGenerator(start: 1).Next().ToString(), "1"),
    ("three ids in a row", () => Three(new IdGenerator(start: 41)), "41,42,43"),
    ("two generators are independent", TwoGenerators, "1,2,1"),
    ("two services share one generator", SharedGenerator, "ORD-1,INV-2,ORD-3"),
    ("each service with its own", OwnGenerators, "ORD-1,INV-1"),
    ("a start of 0", () => new IdGenerator(start: 0).Next().ToString(), "throws ArgumentOutOfRangeException"),
];

int passed = 0;
foreach (var (check, run, expected) in checks)
{
    string got;
    try
    {
        got = run();
    }
    catch (Exception e)
    {
        got = $"throws {e.GetType().Name}";
    }

    bool ok = got == expected;
    if (ok)
    {
        passed++;
    }

    Console.WriteLine(ok ? $"{check}: ok" : $"{check}: FAIL, {got} instead of {expected}");
}

Console.WriteLine($"{passed} of {checks.Length} checks pass");
return passed == checks.Length ? 0 : 1;

static string Three(IdGenerator ids) => $"{ids.Next()},{ids.Next()},{ids.Next()}";

static string TwoGenerators()
{
    var a = new IdGenerator(start: 1);
    var b = new IdGenerator(start: 1);
    return $"{a.Next()},{a.Next()},{b.Next()}";
}

static string SharedGenerator()
{
    var ids = new IdGenerator(start: 1);
    var orders = new OrderService(ids);
    var invoices = new InvoiceService(ids);
    return $"{orders.Create()},{invoices.Create()},{orders.Create()}";
}

static string OwnGenerators()
{
    var orders = new OrderService(new IdGenerator(start: 1));
    var invoices = new InvoiceService(new IdGenerator(start: 1));
    return $"{orders.Create()},{invoices.Create()}";
}

class IdGenerator
{
    public IdGenerator(int start) => throw new NotImplementedException();

    public int Next() => throw new NotImplementedException();
}

class OrderService
{
    public OrderService(IdGenerator ids) => throw new NotImplementedException();

    // An order id such as ORD-1.
    public string Create() => throw new NotImplementedException();
}

class InvoiceService
{
    public InvoiceService(IdGenerator ids) => throw new NotImplementedException();

    // An invoice id such as INV-2.
    public string Create() => throw new NotImplementedException();
}

Output (exit status 1)

the first id: FAIL, throws NotImplementedException instead of 1
three ids in a row: FAIL, throws NotImplementedException instead of 41,42,43
two generators are independent: FAIL, throws NotImplementedException instead of 1,2,1
two services share one generator: FAIL, throws NotImplementedException instead of ORD-1,INV-2,ORD-3
each service with its own: FAIL, throws NotImplementedException instead of ORD-1,INV-1
a start of 0: FAIL, throws NotImplementedException instead of throws ArgumentOutOfRangeException
0 of 6 checks pass

Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run idgen.cs

Write IdGenerator and the two services

Copy idgen.cs. Keep the next id in an instance field, refuse a start below 1 with ArgumentOutOfRangeException.ThrowIfLessThan(start, 1), and let Next return the current number and move on. Each service stores its generator in a readonly field. Run dotnet run idgen.cs until it prints 6 of 6 checks pass.

Then check yourself on static members, static constructors and shared state:

Practice that adapts to you

Practise this lesson

7 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 7 What can a static method not do without help?

    Choose one answer.

    Show the answer: Question 1 of 7

    Answer: Read an instance field, because it has no object to read it from

    No object, no instance fields: that is both the limit and the test of a static member.

  2. Question 2 of 7 What happens to the line var f = new Fares(); when Fares is a static class?

    Choose one answer.

    Show the answer: Question 2 of 7

    Answer: It does not compile: a static class cannot be instantiated

    Members of a static class are reached through its name, Fares.Total(...), and never through an object.

  3. Question 3 of 7 A static PriceCalculator.Calculate(order) reads a static field CurrentDiscount that the checkout screen sets. Two customers check out at the same time. What is the risk?

    Choose one answer.

    Show the answer: Question 3 of 7

    Answer: One customer's discount can be applied to the other's order, because the field is one value for the process

    A static method is safe to share when it works only on what it is given. A static field that holds one user's data is not.

  4. Question 4 of 7 A class has static field initializers and a static constructor. Which runs first?

    Choose one answer.

    Show the answer: Question 4 of 7

    Answer: The static field initializers, in the order they are written

    Initializers first, top to bottom, then the static constructor: one sequence, once, before the first use.

  5. Question 5 of 7 Which declaration is a valid static constructor of class Tax?

    Choose one answer.

    Show the answer: Question 5 of 7

    Answer: static Tax() { … }

    static, the class name and an empty parameter list: the runtime calls it, at most once.

  6. Question 6 of 7 Every test now calls StaticOrderNumbers.ResetForTests() first. The test runner runs tests of different classes in parallel. What can still happen?

    Choose one answer.

    Show the answer: Question 6 of 7

    Answer: One test resets the counter while another is between its two orders, and both get wrong numbers

    Resetting shared state is a patch for one order of tests. Giving each test its own object removes the sharing.

  7. Question 7 of 7 The app needs every checkout to share one order counter, but each test needs its own. Which design gives both?

    Choose one answer.

    Show the answer: Question 7 of 7

    Answer: Checkout takes an OrderNumbers in its constructor; the app passes one shared object, each test a new one

    Passing dependencies in lets each caller choose their lifetime: one for the whole app, one per test.

NanoID, ULID & Snowflake ID Generator Compare sortable ULIDs and random IDs with the counter numbers of this lesson, which repeat in every new process.

Interview questions

Fresher · When should a C# method be static?

When it needs nothing but its parameters and other static members. A static method belongs to the type, so it has no object and cannot read instance fields unless an object is passed to it; Math.Max(a, b) is static because the answer depends only on a and b. A method that works on one object’s data, such as account.Withdraw(500m), must be an instance method. Make a method static for what it needs, not to save typing new: a static method that reaches for shared static fields drags global state into every caller.

  1. Fresher to mid

    Unit tests for an InvoiceNumbering class pass when run one at a time, fail when the whole suite runs, and fail differently when the suite runs in parallel. The class keeps the next invoice number in a static field. Explain what is happening and how you would change the design.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A service reads a timeout from configuration in a static constructor. One deployment shipped the value 30s instead of 30. After the configuration was corrected, every request still failed with TypeInitializationException until the service restarted. Explain why, and how you would load the setting so that this cannot happen again.

    Model answer, Pro. First step and follow-ups

  3. Fresher to mid

    A static readonly List<string> AllowedCountries validates addresses. After one test adds a country to it, other tests start accepting addresses they should reject, and the same bug once reached production. Why did readonly not protect the list, and what would you replace it with?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A static member belongs to the type and exists once in the program; static classes hold only static members and are never created.
  • A static constructor runs at most once, just before the type is first used, right after the static field initializers.
  • Shared state in a static field ties tests to their order; pass an object in, so that each test creates its own and the application shares one.

Terms in this lesson

static class
A class declared static: it holds only static members, cannot be created with new and cannot be inherited from. Math and Console are static classes.
static constructor
A parameterless constructor marked static, such as static Tax() { }. The runtime runs it at most once, just before the type is first used, to initialise the type's static fields.
static member
A field, property, method or event declared static. It belongs to the type rather than to any object, exists once in the program, and is reached through the type's name, as in Ticket.Issued.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.