常量引用的最佳方法或替代方法是什么?

Leo*_*ele 2 c# const-correctness compile-time-constant

出于此问题的目的,"常量引用"是对对象的引用,您无法从该对象调用修改对象或修改其属性的方法.

我想要这样的东西:

Const<User> user = provider.GetUser(); // Gets a constant reference to an "User" object
var name = user.GetName(); // Ok. Doesn't modify the object
user.SetName("New value"); // <- Error. Shouldn't be able to modify the object
Run Code Online (Sandbox Code Playgroud)

理想情况下,我会使用自定义属性(例如[Constant])标记不修改实例的类的每个方法,并且只能从常量引用调用这些方法.如果可能,在编译期间调用其他方法将导致错误.

我的想法是,我可以返回一个只读引用,并确保它不会被客户端修改.

Dai*_*Dai 5

您所指的技术称为" const-correctness",它是C++和Swift的语言特性,但不幸的是C#,但是您通过使用自定义属性来处理某些事情,因为这样您可以通过Roslyn强制执行它延伸 - 但这是一个兔子洞.

或者,使用接口有一个更简单的解决方案:因为C#(我认为CLR也不支持const-correctness(我们最接近的是readonly字段修饰符).NET基类库设计者添加了"只读"接口"通用可变类型,允许对象(可变或不可变)通过仅暴露不可变操作的接口公开其功能.一些例子包括IReadOnlyList<T>,IReadOnlyCollection<T>,IReadOnlyDictionary<T>-而这些都是枚举类型的技术是很好的奇异物体了.

这种设计的优点是可以使用支持接口但不支持const的任何语言.

  1. 对于项目中需要公开数据而没有被更改风险的每种类型(class,struct等),或者任何不可变操作,然后创建一个不可变的接口.
  2. 修改您的消费代码以使用这些接口而不是具体类型.

像这样:

假设我们有一个可变类User和一个消费服务:

public class User
{
    public String UserName     { get; set; }

    public Byte[] PasswordHash { get; set; }
    public Byte[] PasswordSalt { get; set; }

    public Boolean ValidatePassword(String inputPassword)
    {
        Hash[] inputHash = Crypto.GetHash( inputPassword, this.PasswordSalt );
        return Crypto.CompareHashes( this.PasswordHash, inputHash );
    }

    public void ResetSalt()
    {
        this.PasswordSalt = Crypto.GetRandomBytes( 16 );
    }
}

public static void DoReadOnlyStuffWithUser( User user )
{
    ...
}

public static void WriteStuffToUser( User user )
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

然后创建一个不可变的接口:

public interface IReadOnlyUser
{
    // Note that the interfaces' properties lack setters.
    String              UserName     { get; }
    IReadOnlyList<Byte> PasswordHash { get; }
    IReadOnlyList<Byte> PasswordSalt { get; }

    // ValidatePassword does not mutate state so it's exposed
    Boolean ValidatePassword(String inputPassword);

    // But ResetSalt is not exposed because it mutates instance state
}
Run Code Online (Sandbox Code Playgroud)

然后修改你的User类和消费者:

public class User : IReadOnlyUser
{
    // (same as before, except need to expose IReadOnlyList<Byte> versions of array properties:
    IReadOnlyList<Byte> IReadOnlyUser.PasswordHash => this.PasswordHash;
    IReadOnlyList<Byte> IReadOnlyUser.PasswordSalt => this.PasswordSalt;
}

public static void DoReadOnlyStuffWithUser( IReadOnlyUser user )
{
    ...
}

// This method still uses `User` instead of `IReadOnlyUser` because it mutates the instance.
public static void WriteStuffToUser( User user )
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

  • 旁注:单独的接口不能保护不友好的调用者,因为他们可以使用reflection/cast /`dynamic`来调用实现类的`SetXxxx`方法.如果是这种情况,则需要 - 接口和代理实现来隐藏实际的类. (3认同)
  • @AlexeiLevenkov虽然在C++中使用`const_cast`没有什么不同.我认为无论如何都不值得为对抗性的图书馆消费者制定对策,因为这是为了消除潜在的错误,而不是安全性. (3认同)