为什么我可以设置私有属性的属性?

Bec*_*ite 3 .net c# .net-core

我有一个类LoginManager,它有一个私有字段currentUser和一个公共属性CurrentUser,不允许自己意外地CurrentUserLoginManager类外部更改其值。

CurrentUser只是{ get; },但我仍然可以更改底层currentUser私有的属性。

例如。

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
Run Code Online (Sandbox Code Playgroud)

登录管理器.cs

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

        public event EventHandler CurrentUserChanged;

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

        //...
    }
Run Code Online (Sandbox Code Playgroud)

用户.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; }

    }
Run Code Online (Sandbox Code Playgroud)

我想要它,这样我就无法CurrentUser在课堂之外进行更改LoginManager。有人能指出我正确的方向吗?

sir*_*ide 5

首先要了解的是,缺少set访问器只会阻止您更改属性本身的值CurrentUser。也就是说,它阻止您将其更改为另一个User对象(或 null)。然而,这些规则仅适用于财产本身,而不适用于财产内部的任何东西。属性的只读性质不是递归的。

User如果您想防止更改中包含的对象内部的属性CurrentUser,则需要有User某种类型的不可变对象,或者必须防止直接访问该对象并提供帮助程序方法,允许从用户检索某些数据而无需能够改变它。因此,这两个选项是隐藏直接访问或使User对象本身以某种方式不可变。

第一个选项是隐藏对User对象的访问:

public User? CurrentUser { get; }

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

public string GetCurrentUserUsername() {
    return CurrentUser?.UserName;
}
Run Code Online (Sandbox Code Playgroud)

上面的内容遵循所谓的德米特法则,但我不认为它经常被使用,因为它不符合人体工程学并且会导致类膨胀。仅当当前用户的许多详细信息对于使用 的代码并不重要LoginManager并且此类代码不需要能够传递User对象时才采用此路线。

第二个选项有一些子选项。您可以创建一个IReadOnlyUser仅公开只读属性的接口:

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
}
Run Code Online (Sandbox Code Playgroud)

此外,您可以像其他答案一样,将User类中的字段设置为只读,无论是使用readonly字段还是仅具有get访问器的属性。这取决于您是否认为在某些情况下您应该能够修改User对象的属性,但不能在LoginManager.

最后,如果您不控制该类User,则可以创建一个包装对象User但仅公开 getter 的外观类:

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
Run Code Online (Sandbox Code Playgroud)

在我看来,没有特定的唯一正确答案。这取决于你想做什么。