什么是恒定引用的最佳方法或替代方法?

时间:2018-11-25 01:17:05

标签: 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

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

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

2 个答案:

答案 0 :(得分:5)

不幸的是,您所指的技术称为"const-correctness",它是C ++和Swift的语言功能,但不是C#的语言功能-但是,您可以通过使用自定义属性来实现某些功能,因为这样您就可以执行通过Roslyn扩展名-但这是一个兔子洞。

或者,有一个使用接口的简单得多的解决方案:因为C#(我也认为CLR也)不支持const-correctness(我们最近的是readonly字段修饰符).NET基类-library设计人员向常见的可变类型添加了“只读接口”,以允许对象(无论是可变的还是不可变的)通过仅公开不可变操作的接口公开其功能。一些示例包括IReadOnlyList<T>IReadOnlyCollection<T>IReadOnlyDictionary<T>-尽管这些都是可枚举的类型,但该技术也适用于单个对象。

此设计的优势在于可以使用支持接口但不支持const正确性的任何语言。

  1. 对于项目中需要公开数据而不会被更改的任何类型(classstruct等),或进行任何不可变的操作,然后创建一个不可变的接口。
  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 )
{
    ...
}

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

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
}

然后修改您的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 )
{
    ...
}

答案 1 :(得分:0)

因此,这是我最初拥有的前两个想法,但并不能完全解决问题。

使用动态对象:

我的第一个想法是创建一个Dynamic Object,如果所调用的方法未使用[Constant]自定义属性进行标记,它将拦截所有成员调用并引发错误。这种方法是有问题的,因为a)我们没有编译器的支持来处理动态对象时检查代码中的错误(即方法名称拼写错误),这可能会导致很多运行时错误; b)我打算经常使用它,并且每次调用方法时都按名称搜索方法名称可能会对性能产生重大影响。

使用RealProxy:

我的第二个想法是使用RealProxy包装实际对象并验证所调用的方法,但这仅适用于从MarshalByRefObject继承的对象。