Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can I set the properties of a privately set property?

Tags:

c#

.net

.net-core

I have a class LoginManager which has a private field currentUser and a public property CurrentUser to not allow myself to accidentally change the value of CurrentUser from outside the LoginManager class.

CurrentUser is { get; } only but I can still change properties in the underlying currentUser which is private.

eg.

Console.WriteLine(loginManager.CurrentUser.ClockedIn.ToString()); // true
loginManager.CurrentUser.ClockedIn = false;
Console.WriteLine(loginManager.CurrentUser.ClockedIn.ToString()); // false
loginManager.CurrentUser.ClockedIn = true;
Console.WriteLine(loginManager.CurrentUser.ClockedIn.ToString()); // true

LoginManager.cs

public class LoginManager
    {
        private User? currentUser { get; set; }
        private readonly ApplicationDbContext dbContext;

        public event EventHandler CurrentUserChanged;

        public User? CurrentUser
        {
            get { return currentUser; }
        }

        //...
    }

User.cs

public class User
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public string Username { get; set; }
        public string Password { get; set; }
        public bool ClockedIn { get; set; }

    }

I want it so that I can't change CurrentUser from outside the LoginManager class. Could anybody please point me in the right direction?

like image 495
Beckam White Avatar asked Dec 29 '25 13:12

Beckam White


1 Answers

The first thing to understand is that the lacking set accessor only prevents you from changing the value of the CurrentUser property itself. That is, it prevents you from changing it to another User object (or null). However, these rules only apply to the property itself, and not anything inside of it. The read-only nature of properties is not recursive.

If you want to prevent changing the properties inside the User object contained in CurrentUser, you'll need to have an immutable User object of some sort, or you must prevent direct access to the object and provide helper methods that allow retrieving certain data from the user without being able to change it. The two options are thus to hide direct access or to make the User object somehow immutable itself.

The first option is hiding access to the User object:

public User? CurrentUser { get; }

public string GetCurrentUserName() {
    return CurrentUser?.Name;
}

public string GetCurrentUserUsername() {
    return CurrentUser?.UserName;
}

The above follows the so-called Law of Demeter, but I don't see it used too often as it is not ergonomic and bloats up classes. Only go this route if many of the details of the current user aren't important to code using the LoginManager and if such code doesn't need to be able to pass around the User object.

The second option has some suboptions. You can create an IReadOnlyUser interface that only exposes read-only properties:

interface IReadOnlyUser {
    public string Name { get; }
    public string Username { get; }
}

public class User : IReadOnlyUser {
    // although these have setters, they cannot be access through variables of type IReadOnlyUser (see below)
    public string Name { get; set; }
    public string Username { get; set; }
}

public class LoginManager {
    // you can use a field here instead of a property since it is private
    private User currentUser;

    // returns an read-only view of the user, so write accessors are not available
    public IReadOnlyUser CurrentUser {
        get { return currentUser; }
    }
}

// usage
var lm = new LoginManager();
Console.WriteLine(lm.CurrentUser.Name);  // okay because IReadOnlyUser has a read accessor for Name
lm.CurrentUser.Name = "foo";   // not allowed because IImutableUser does not expose a write accessor
}

Additionally, you can do as in the other answer and make the fields in the User class read-only, either with readonly fields or with properties that only have get accessors. It depends on whether you feel there are cases where you should be able to modify properties of User objects, just not in the context of the LoginManager.

Finally, if you do not control the User class, you can create a facade class that wraps a User object but only exposes getters:

public class ReadOnlyUser {
    private User user;
    public ReadOnlyUser(User user) { this.user = user; }
    public string Name { get { return user.Name; } }
}

// in LoginManager
private ReadOnlyUser currentUser;
public ReadOnlyUser CurrentUser { 
    get { return currentUser; } 
    set {
        currentUser = new ImmutableUser(value);
    }
}

// usage
var lm = new LoginManager();
lm.CurrentUser = userFromSomewhereElse;
Console.Log(lm.CurrentUser.Name);  // okay because ReadOnlyUser exposes this property from the underlying User class
lm.CurrentUser.Name = "foo";  // not allowed because ReadOnlyUser doesn't have a setter for Name

There is, in my mind, no particular single right answer. It depends on what you want to do.

like image 165
siride Avatar answered Jan 01 '26 03:01

siride



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!