Why can I set the properties of a privately set property?
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?
Solution 1:
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.
Solution 2:
I think I see the problem, the private CurrentUser
instances get
and set
properties are exposed outside the class since those member variables are public
in scope.
Why not make the User
properties private
?
public class User
{
private Guid Id { get; set; }
private string Name { get; set; }
private string Username { get; set; }
private string Password { get; set; }
private bool ClockedIn { get; set; }
}
Or if you need to get
the properties mark private set
:
public class User
{
public Guid Id { get; private set; }
public string Name { get; private set; }
public string Username { get; private set; }
public string Password { get; private set; }
public bool ClockedIn { get; private set; }
}
Or explicitly make them readonly
so they can only be initialized in the Constructor
:
public class User
{
public readonly Guid Id { get; private set; }
public readonly string Name { get; private set; }
public readonly string Username { get; private set; }
public readonly string Password { get; private set; }
public readonly bool ClockedIn { get; private set; }
public User(Guid Id, string Name, string Username, string Password, bool ClockedIn)
{
this.Id = Id;
this.Name = NameName;
this.Username = Username;
this.Password = Password;
this.ClockedIn = ClockedIn;
}
}