实现安全访问容器



假设我有一个表示容器的类。该容器包含一些带有 get 和 set 修饰符的公共属性。

我想要的是实现一些机制,该机制将在运行时启用访问并禁用对这些属性引用的访问。

例如,当某些布尔标志为 true 时,可以访问这些属性。这意味着:

SomeClass.Property1;

不会生成异常,并将返回对象。

但是,当它为 false 时,上面的代码行将引发异常。

当然,当使用一些布尔键并在每个属性的网关上检查它时,也可以这样做。

我的问题是,是否有可能实现这样的机制,为类中的所有属性施加这些限制,而无需在每次访问这些属性时断言这些条件。

感谢您的帮助。

看起来空对象模式可能会有所帮助。

显示如何在您的案例中使用它的简单代码。与您想要的不完全相同,但它不需要在每次访问对象的属性和方法时断言条件。

实体:

abstract class AbstractEntity
{
public abstract void DoSomething();
public abstract void DoSomethingElse();
public abstract int Property { get; set; }
}
class RealEntity : AbstractEntity
{
public override void DoSomething()
{
Console.WriteLine("Something");
}
public override void DoSomethingElse()
{
Console.WriteLine("Something else");
}
public override int Property { get; set; }
}
class NullEntity : AbstractEntity
{
public override void DoSomething()
{
// do nothing or throw exception
}
public override void DoSomethingElse()
{
// do nothing or throw exception
}
public override int Property
{
get { throw new Exception(); }
set { throw new Exception(); }
}
}

AccessContainer的简单示例:

class AccessContainer
{
private RealEntity _entity = new RealEntity();
private NullEntity _nullEntity = new NullEntity();
private bool _access = true;
public AbstractEntity Entity
{
get => _access ? (AbstractEntity) _entity : (AbstractEntity) _nullEntity;
}
public void OpenAccess()
{
_access = true;
}
public void DenyAccess()
{
_access = false;
}
}

用法:

var container = new AccessContainer();
container.Entity.DoSomething(); // prints something
var prop = container.Entity.Property; // access to property
container.DenyAccess();
container.Entity.DoSomething(); // do nothing
container.OpenAccess();
container.Entity.DoSomething(); // prints something again
container.DenyAccess();
var prop2 = container.Entity.Property; // exception

您所要求的内容本身并不存在,您将不得不编写某种包装功能来测试是否授予了可访问性。

public interface IAccessOwner {
bool Accessible { get; } 
} 
[DebuggerDisplay("Accessible: {Accessible,nq} - Value: {ToString()}")]
[DebuggerTypeProxy(typeof(RestrictedObject<>.DebuggerProxy))]
public class RestrictedObject<T> {
private readonly IAccessOwner _owner;
private T _value;
public RestrictedObject(IAccessOwner owner, T initialValue) 
: this(owner) {
_value = initialValue;
}
public RestrictedObject(IAccessOwner owner) {
_owner = owner ?? throw new ArgumentNullException(nameof(owner));
} 
public T Value {
get {
ThrowIfInaccessible();
return _value;
}
set {
ThrowIfInaccessible();
_value = value;
} 
}
public bool Accessible => _owner.Accessible;
public override string ToString() {
if (!Accessible)
return "<Inaccessible>"; // ToString should never throw 
if (_value is { } val) 
return val.ToString();
return "<null>";
} 
private void ThrowIfInaccessible() {
if(!Accessible)
throw new InvalidOperationException("Not accessible!");
}
// explicit operator to cast directly to value
public static explicit operator T(RestrictedObject<T> ro) {
ro.ThrowIfInaccessible();
return ro.Value;
}
private sealed class DebuggerProxy {
public bool Accessible { get; } 
public T Value { get; } 
public DebuggerProxy(RestrictedObject<T> ro) {
bool acc = Accessible = ro.Accessible;
if (acc) 
Value = ro._value;
} 
} 
} 

然后,可以在类中使用此类型的属性:

public class MyClass : IAccessOwner {
private readonly RestrictedObject<int> _prop1;
private readonly RestrictedObject<string> _prop2;
public MyClass(int someVal) {
_prop1 = new RestrictedObject<int>(this, someVal);
_prop2 = new RestrictedObject<string>(this);
Accessible = true;
} 
public bool Accessible { get; private set; } 
// you determine how you want to toggle the above property. 
// Exposing it publicly defeats the purpose of all of this, 
// but for demo purposes only:
public void DenyAccess() {
Accessible = false;
} 
public void AllowAccess() {
Accessible = true;
}
// these properties will throw exceptions if the owner 
// (this object) is not currently accessible. 
public int Prop1 {
get => _prop1.Value;
set => _prop1.Value = value;
}
public string Prop2 {
get => _prop2.Value;
set => _prop2.Value = value;
} 
// alternatively return the wrapper itself 
// allowing you to control the accessibility 
// even after returning the object 
public RestrictedObject<string> AltProp2 => _prop2;
} 

然后,您将像下面这样使用它(显然异常将停止执行,处理已被省略):

var mc = new MyClass(3);
Console.WriteLine(mc.Prop1); // prints 3
Console.WriteLine(mc.Prop2); // prints null
var temp = mc.AltProp2; // use the wrapper directly
mc.Prop2 = "Hello";
Console.WriteLine(mc.Prop2); // prints Hello
Console.WriteLine(temp.Value); // prints Hello
Console.WriteLine((string)temp); // explicit operator, prints Hello
mc.DenyAccess();
mc.Prop1 = 33; // throws! 
Console.WriteLine(mc.Prop1); // throws! 
Console.WriteLine(mc.Prop2); // throws! 
Console.WriteLine(temp.Value); // throws! 
Console.WriteLine((string)temp); // explicit operator, throws!
Console.WriteLine(temp); // prints "<Inaccessible>" 
mc.AllowAccess();
string temp3 = (string)temp; // "Hello", explicit operator works again
mc.Prop1 = 22; // as do our setters 
mc.Prop2 = "Goodbye";
if (temp.Accessible) {
Console.WriteLine(temp); // "Goodbye" 
} 

唯一不会引发异常的是RestrictedObject类型本身的ToString覆盖,因为您永远不应该从ToString抛出。相反,我们只是返回<Inaccessible>.

我们还通过DebuggerTypeProxyAttribute更改了RestrictedObject<T>在调试器中的显示方式。如果有人尝试检查对象的属性,他们将看到Accessible属性,并且只有在true时才会显示包装对象的Value。否则,将显示default(T)(null表示引用类型,0表示整型,false表示bool)。此外,通过使用DebuggerDisplayAttribute,我们自定义了对象的折叠版本的显示,以便它在我们的自定义ToString旁边显示Accessible属性。

请注意,这仍然有一个缺点,即如果有人检索内部/包装的对象并且后来拒绝了可访问性,他们仍然拥有该对象。你无法做任何事情来真正防范这种情况。您还必须意识到(并接受)任何使用反射的人都可以更改或访问对象的状态,如果他们真的愿意的话。

我还要指出,这违反了正常的 C# 实践,这些实践通常规定属性不应引发异常。Microsoft自己的指导方针也说了这么多,尽管他们使用术语"避免"而不是"不要"。框架本身违反了这一"规则"。如果您要违反最小意外原则,至少要礼貌地为您的 API 使用者记录此行为

最新更新