常量引用的最佳方法或替代方法是什么?
What is the best approach or alternative to constant references?
就此问题而言,'constant reference' 是对对象的引用,您无法从中调用修改对象或修改其属性的方法。
我想要这样的东西:
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]
)标记 class 的每个不修改实例的方法,并且只有那些方法可以从常量引用中调用.如果可能,在编译期间调用其他方法将导致错误。
我的想法是,我可以 return 一个只读引用,并确保它不会被客户端修改。
所以,这是我最初的两个想法,但并没有完全解决问题。
使用动态对象:
我的第一个想法是创建一个 Dynamic Object
,它将拦截所有成员调用并在被调用的方法未标记有 [Constant]
自定义属性时抛出错误。这种做法是有问题的,因为 a) 在处理动态对象时,我们没有编译器的支持来检查代码中的错误(即方法名拼写错误),这可能会导致很多运行时错误; b) 我打算经常使用它,每次调用方法时按名称搜索方法名称可能会对性能产生相当大的影响。
使用 RealProxy:
我的第二个想法是使用 RealProxy
包装真实对象并验证被调用的方法,但这只适用于继承自 MarshalByRefObject
的对象。
您所指的技术称为 "const
-correctness",它是 C++ 和 Swift 的语言功能,但不是 C#,不幸的是 - 但是您正在使用自定义属性因为这样你就可以通过 Roslyn 扩展来强制执行它 - 但这是一个兔子洞。
或者,有一个使用接口的更简单的解决方案:因为 C#(我认为 CLR 也是)不支持常量正确性(我们拥有的最接近的是 readonly
字段修饰符).NET 基础-class-库设计者将 "read-only interfaces" 添加到常见的可变类型,以允许对象(无论是可变的还是不可变的)通过仅公开不可变操作的接口公开其功能。一些示例包括 IReadOnlyList<T>
、IReadOnlyCollection<T>
、IReadOnlyDictionary<T>
- 虽然这些都是可枚举类型,但该技术也适用于单个对象。
这种设计的优点是可以在任何支持接口但不支持常量正确性的语言中工作。
- 对于您项目中需要公开数据而没有被更改风险的每种类型(
class
、struct
等)或任何不可变操作,然后创建一个不可变接口。
- 修改您的使用代码以使用这些接口而不是具体类型。
像这样:
假设我们有一个可变的 class 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
class和消费者:
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 )
{
...
}
就此问题而言,'constant reference' 是对对象的引用,您无法从中调用修改对象或修改其属性的方法。
我想要这样的东西:
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]
)标记 class 的每个不修改实例的方法,并且只有那些方法可以从常量引用中调用.如果可能,在编译期间调用其他方法将导致错误。
我的想法是,我可以 return 一个只读引用,并确保它不会被客户端修改。
所以,这是我最初的两个想法,但并没有完全解决问题。
使用动态对象:
我的第一个想法是创建一个 Dynamic Object
,它将拦截所有成员调用并在被调用的方法未标记有 [Constant]
自定义属性时抛出错误。这种做法是有问题的,因为 a) 在处理动态对象时,我们没有编译器的支持来检查代码中的错误(即方法名拼写错误),这可能会导致很多运行时错误; b) 我打算经常使用它,每次调用方法时按名称搜索方法名称可能会对性能产生相当大的影响。
使用 RealProxy:
我的第二个想法是使用 RealProxy
包装真实对象并验证被调用的方法,但这只适用于继承自 MarshalByRefObject
的对象。
您所指的技术称为 "const
-correctness",它是 C++ 和 Swift 的语言功能,但不是 C#,不幸的是 - 但是您正在使用自定义属性因为这样你就可以通过 Roslyn 扩展来强制执行它 - 但这是一个兔子洞。
或者,有一个使用接口的更简单的解决方案:因为 C#(我认为 CLR 也是)不支持常量正确性(我们拥有的最接近的是 readonly
字段修饰符).NET 基础-class-库设计者将 "read-only interfaces" 添加到常见的可变类型,以允许对象(无论是可变的还是不可变的)通过仅公开不可变操作的接口公开其功能。一些示例包括 IReadOnlyList<T>
、IReadOnlyCollection<T>
、IReadOnlyDictionary<T>
- 虽然这些都是可枚举类型,但该技术也适用于单个对象。
这种设计的优点是可以在任何支持接口但不支持常量正确性的语言中工作。
- 对于您项目中需要公开数据而没有被更改风险的每种类型(
class
、struct
等)或任何不可变操作,然后创建一个不可变接口。 - 修改您的使用代码以使用这些接口而不是具体类型。
像这样:
假设我们有一个可变的 class 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
class和消费者:
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 )
{
...
}