You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

C#中如何实现Settings类属性的特定访问控制?

实现C#中类似C++友元类的访问控制方案

这个需求确实有点绕——既要让Settings保持可序列化的公开状态,又要严格限制只有Foo类能直接操作它的属性,同时还要满足直接访问、禁止非法路径访问等要求。咱们一步步拆解可行的方案:

方案1:密钥控制+内部接口(最贴合需求,推荐)

这个方案通过一个只有Foo能创建的"访问密钥",配合内部接口来精准控制Settings的属性访问,同时完全满足序列化要求:

// 仅允许Foo类实例化的密钥类,构造函数设为internal
internal sealed class SettingsAccessKey
{
    internal SettingsAccessKey() { }
}

[Serializable]
public class Settings
{
    // 私有字段,用序列化特性标记,让序列化框架可访问(支持Newtonsoft.Json/System.Text.Json)
    [JsonProperty]
    private int _int1;
    [JsonProperty]
    private int _int2;
    [JsonProperty]
    private int _int3;

    // 仅内部可见的访问接口,只有Foo能使用
    internal interface ISettingsAccessor
    {
        int Int1 { get; set; }
        int Int2 { get; set; }
        int Int3 { get; set; }
    }

    // 显式实现接口的内部访问器
    private sealed class Accessor : ISettingsAccessor
    {
        private readonly Settings _settings;

        public Accessor(Settings settings) => _settings = settings;

        public int Int1
        {
            get => _settings._int1;
            set => _settings._int1 = value;
        }

        public int Int2
        {
            get => _settings._int2;
            set => _settings._int2 = value;
        }

        public int Int3
        {
            get => _settings._int3;
            set => _settings._int3 = value;
        }
    }

    // 仅持有合法密钥的调用者能获取访问器
    internal ISettingsAccessor GetAccessor(SettingsAccessKey key)
    {
        if (key == null) throw new UnauthorizedAccessException("无访问权限");
        return new Accessor(this);
    }

    // 禁止外部直接实例化Settings
    internal Settings() { }
}

class FooData
{
    private Settings _settings;

    // 对外暴露整体的Settings实例,但类型为object(避免外部直接操作)
    public object SettingsWrapper
    {
        get => _settings;
        set => _settings = value as Settings;
    }

    // 内部方法,仅Foo能调用获取真实Settings实例
    internal Settings GetSettings() => _settings;
}

class Foo
{
    // Foo专属的访问密钥,外部无法创建
    private static readonly SettingsAccessKey _accessKey = new SettingsAccessKey();
    public FooData fooData { get; set; } = new FooData();

    // 直接暴露Settings的属性,满足需求1
    public int int1
    {
        get => fooData.GetSettings().GetAccessor(_accessKey).Int1;
        set => fooData.GetSettings().GetAccessor(_accessKey).Int1 = value;
    }

    public int int2
    {
        get => fooData.GetSettings().GetAccessor(_accessKey).Int2;
        set => fooData.GetSettings().GetAccessor(_accessKey).Int2 = value;
    }

    public int int3
    {
        get => fooData.GetSettings().GetAccessor(_accessKey).Int3;
        set => fooData.GetSettings().GetAccessor(_accessKey).Int3 = value;
    }

    // 整体获取/设置Settings实例,满足需求3
    public Settings Settings
    {
        get => fooData.GetSettings();
        set => fooData.SettingsWrapper = value;
    }
}

方案效果验证:

  • 需求1:fooInstance.int1 = 3;完全可行,Foo通过专属密钥拿到了合法访问器
  • 需求2:外部new Settings()会编译报错,因为构造函数是internal
  • 需求3:外部可以var settings = fooInstance.Settings;拿到实例,但无法直接访问任何属性(字段是私有,无public属性)
  • 需求4:序列化框架通过[JsonProperty]可以访问私有字段,完全支持序列化

方案2:反射实现(不推荐,仅作参考)

你提到的反射方案确实可行,但缺点很明显:性能差、无编译时检查、安全性低,只能作为应急方案:

[Serializable]
public class Settings
{
    public int int1 { get; set; }
    public int int2 { get; set; }
    public int int3 { get; set; }

    // 禁止外部直接实例化
    internal Settings() { }
}

class FooData
{
    public Settings settings { get; set; }
}

class Foo
{
    public FooData fooData { get; set; } = new FooData();

    // 通过反射访问Settings属性
    public int int1
    {
        get => (int)typeof(Settings).GetProperty(nameof(Settings.int1)).GetValue(fooData.settings);
        set => typeof(Settings).GetProperty(nameof(Settings.int1)).SetValue(fooData.settings, value);
    }

    // 同理实现int2、int3...

    // 整体获取/设置时,可通过栈帧检查调用方(可靠性有限)
    public Settings Settings
    {
        get => fooData.settings;
        set
        {
            var callerType = new StackTrace().GetFrame(1).GetMethod().DeclaringType;
            if (callerType != typeof(Foo))
                throw new UnauthorizedAccessException("仅Foo类可设置Settings");
            fooData.settings = value;
        }
    }
}

方案缺点:

  • 每次属性访问都有反射性能开销
  • 无法在编译时阻止外部非法访问,只能靠运行时抛异常
  • 外部若同样使用反射,可轻易绕过限制,安全性极低

关于C#友元类的疑问

C#确实没有像C++那样直接的友元类功能,但方案1通过"密钥+内部接口"的方式,已经精准模拟了友元的效果——只有Foo类能合法访问Settings的属性,其他类即使拿到Settings实例也无法操作其内部数据。

如果不想用序列化特性标记私有成员,也可以把Settings的属性设为internal,再用[InternalsVisibleTo("YourFooAssembly")]让Foo所在程序集访问,但这样整个程序集的类都能访问Settings属性,精准度不如方案1。

内容的提问来源于stack exchange,提问作者rustyBucketBay

火山引擎 最新活动