Start learning free

LESSON 559 OF 572 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Thermostat in C#: Private Fields and Safe Methods

Practice O(1) Read the full explanation

A thermostat keeps a target temperature, and the target must always stay between 10 and 30 degrees. If any code could change the target directly, nothing would stop it from setting 95. So the target is a private field: only the thermostat's own methods can change it, and every one of them keeps it between 10 and 30.

Implement the class Thermostat:

  • Thermostat() creates a thermostat whose target is 20.
  • setTarget(t) sets the target to t. A value below 10 sets it to 10, and a value above 30 sets it to 30.
  • warmer() raises the target by 1 and cooler() lowers it by 1, but neither ever takes it outside 10 to 30: at 30, warmer() leaves it at 30, and at 10, cooler() leaves it at 10.
  • getTarget() returns the target. It is the only way for other code to read it.

Keep the target private the way the Access Modifiers lesson does in your language: self._target in Python (private by convention), a #target field in JavaScript, and the private keyword in Java, C++, C#, TypeScript, Kotlin, Scala and Swift.

The input is a list of operations, applied in order to one new Thermostat. The output lists what the getTarget calls return, in order. The starter code spells these names the way your language does: set_target and get_target in Python, SetTarget, Warmer, Cooler and GetTarget in C#.

 

Example 1:

Input:
    getTarget()
    warmer()
    warmer()
    getTarget()
    cooler()
    getTarget()
Output: 20 22 21
Explanation: A new thermostat starts at 20. Two warmer() calls take it to 22, and cooler() brings it back to 21.

Example 2:

Input:
    setTarget(35)
    getTarget()
    warmer()
    getTarget()
    cooler()
    getTarget()
Output: 30 30 29
Explanation: 35 is above 30, so the target becomes 30. warmer() can't go past 30, so the target stays 30.
cooler() lowers it to 29.

Example 3:

Input:
    setTarget(4)
    getTarget()
    cooler()
    getTarget()
    setTarget(10)
    warmer()
    getTarget()
Output: 10 10 11
Explanation: 4 is below 10, so the target becomes 10, and cooler() can't go below 10. setTarget(10) keeps 10,
since 10 itself is allowed, and warmer() raises it to 11.

 

Constraints:

  • -1000 <= t <= 1000
  • A test case has from 1 to 1000 operations, and at least one of them is getTarget.
STEP 1 OF 8
    Ctrl + Enter to check

    C# · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

    Thermostat in C#, explained

    Problem overview

    A thermostat keeps a target temperature, and the target must always stay between 10 and 30 degrees. The Thermostat class makes that true by keeping the target in a private field. Only the class's own methods can change it, and each one keeps it inside the range.

    • A new thermostat has target 20.
    • SetTarget(t) sets the target, turning anything below 10 into 10 and anything above 30 into 30.
    • Warmer() raises it by 1 and Cooler() lowers it by 1, never leaving 10 to 30.
    • GetTarget() returns it, and is the only way for other code to read it.

    Starting at 20, two Warmer() calls give 22 and a Cooler() gives 21. After SetTarget(35) the target is 30, and Warmer() leaves it at 30.

    Approach

    If the field were public, any code could write thermostat.target = 95 and nothing would stop it. Making it private moves that control into the class. Code outside Thermostat that tries to use the field gets a compile error, so the rule only has to be written in one place.

    That place is SetTarget. Warmer and Cooler do not repeat the range check: they call SetTarget with target + 1 or target - 1, and SetTarget turns 31 back into 30 or 9 back into 10. If the limits ever change, one method changes.

    Step by step

    1. Declare private int target. Only code inside the class can use it.
    2. Constructor: set target to 20.
    3. In SetTarget, an if for t < 10 sets the target to 10.
    4. An else if for t > 30 sets it to 30.
    5. An else sets it to t. The values 10 and 30 themselves are allowed, so they end up here unchanged.
    6. Warmer calls SetTarget(target + 1) and Cooler calls SetTarget(target - 1).
    7. GetTarget returns target.

    The solution in C#

    class Thermostat {
        private int target;
    
        public Thermostat() {
            target = 20;
        }
    
        public void SetTarget(int t) {
            if (t < 10) {
                target = 10;
            } else if (t > 30) {
                target = 30;
            } else {
                target = t;
            }
        }
    
        public void Warmer() {
            SetTarget(target + 1);
        }
    
        public void Cooler() {
            SetTarget(target - 1);
        }
    
        public int GetTarget() {
            return target;
        }
    }
    

    Using it:

    var t = new Thermostat();
    Console.WriteLine(t.GetTarget());
    t.Warmer(); t.Warmer();
    Console.WriteLine(t.GetTarget());
    t.SetTarget(35);
    Console.WriteLine(t.GetTarget());
    t.Warmer();
    Console.WriteLine(t.GetTarget());
    t.SetTarget(4);
    t.Cooler();
    Console.WriteLine(t.GetTarget());
    

    This prints:

    20
    22
    30
    30
    10
    

    What private does

    Writing t.target in code outside the class does not compile. The compiler reports:

    error CS0122: 'Thermostat.target' is inaccessible due to its protection level
    

    This is a check made when the program is compiled, so a thermostat with a target of 95 can't be created by accident. Inside the class, every method can use target freely. GetTarget lets other code read the value without letting it change it. In C#, members without a modifier inside a class are private by default, but writing private says what you mean.

    Edge cases and common mistakes

    • Refusing instead of clamping. A value that is too high or too low is not ignored: SetTarget(4) gives 10, and SetTarget(-1000) gives 10 as well.
    • Boundaries. 10 and 30 are allowed. Using t <= 10 would work by accident here, but t < 10 says the rule as stated.
    • Repeating the check in Warmer. It works but duplicates the rule. Going through SetTarget keeps it in one place.
    • At the edge. At 30, Warmer() leaves 30. At 10, Cooler() leaves 10.

    Testing

    • New thermostat: GetTarget() is 20. After two Warmer(): 22. After Cooler(): 21.
    • SetTarget(35): 30. Warmer(): still 30. Cooler(): 29.
    • SetTarget(4): 10. Cooler(): still 10. SetTarget(10), then Warmer(): 11.

    Another way to clamp

    Forcing a value into a range is called clamping, and .NET has a method for it. Math.Clamp(t, 10, 30) returns 10 for anything below 10, 30 for anything above 30, and t otherwise. SetTarget could be a single line, and it behaves exactly like the if chain:

    public void SetTarget(int t) {
        target = Math.Clamp(t, 10, 30);
    }
    

    With it, SetTarget(-1000) gives 10, SetTarget(1000) gives 30, and SetTarget(10) followed by Warmer() gives 11. The lesson spells the branches out so you can see each case; in a file of your own, add using System; to use Math.

    Each thermostat has its own target

    The private field belongs to the object. If you create two thermostats and call Warmer() on the first, the first returns 21 from GetTarget() and the second still returns 20. Private does not mean shared: it means that only code inside the class can touch the field, whichever object it belongs to.

    Why not a public field and trust the callers

    With a public field, every piece of code that uses a thermostat would have to remember the 10 to 30 rule, and one forgotten check could put the object in an invalid state that other code then relies on. With a private field, the class can promise that every thermostat it creates is valid at all times. This is the main reason to hide a field: the class keeps its own promises, and its users can't break them.

    Key takeaways

    • A private field can be used only inside its class.
    • Put the validity rule in one method and let the others call it.
    • Getter methods let other code read a private value without changing it.

    These properties hold even for the extreme inputs the statement allows, such as -1000 and 1000, because every path ends in SetTarget.

    Keep going with C#500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free