如何针对特定模块选择性限制类属性访问?
针对你遇到的这个场景——既要满足DataContractJsonSerializer的反序列化需求,又要给不同模块分配不同的属性访问权限,同时避免大量重复代码,这里有几个实用的方案,按推荐优先级排序:
方案一:显式接口拆分读写权限 + T4模板自动生成(推荐)
这个方案能在编译时严格控制权限,同时通过模板减少重复代码,非常适合你有20多个类的场景。
步骤1:定义只读/可写接口
为每个实体类创建两个接口:一个只读接口暴露所有get属性,一个可写接口继承只读接口并添加set能力:
// 公开的只读接口,供ThingView等只读模块使用 public interface IReadOnlyThing { int Id { get; } string Name { get; } // 其他所有需要只读访问的属性 } // 内部可写接口,仅对当前程序集和授权的编辑模块可见 internal interface IThing : IReadOnlyThing { new int Id { get; set; } new string Name { get; set; } // 对应只读接口的所有可写属性 }
步骤2:实现实体类
让Thing类实现可写接口,并将属性的setter设为internal(满足DataContractJsonSerializer的要求):
[DataContract] internal class Thing : IThing { [DataMember] public int Id { get; internal set; } [DataMember] public string Name { get; internal set; } // 显式实现只读接口(可选,进一步强化访问控制) int IReadOnlyThing.Id => Id; string IReadOnlyThing.Name => Name; }
步骤3:模块依赖控制
- ThingView模块:仅引用
IReadOnlyThing接口,编译时只能访问get方法,完全无法触及setter。 - ThingEditor模块:通过
InternalsVisibleToAttribute授权其访问当前程序集的内部成员,直接使用IThing或Thing类即可读写所有属性。
步骤4:用T4模板减少重复代码
因为你有20多个类,手动写接口太繁琐,可以用T4模板自动扫描所有标记了[DataContract]的类,自动生成对应的IReadOnlyXXX和IXXX接口。模板的核心逻辑是反射遍历程序集里的DataContract类,提取所有DataMember属性,然后生成接口代码。这样一次编写模板,所有类的接口都能自动生成。
方案二:InternalsVisibleToAttribute 快速授权(简单场景首选)
如果不需要最严格的编译时控制,只是想快速区分读写权限,这个方案最省事:
- 将
Thing类的所有属性setter设为internal,满足反序列化要求:
[DataContract] class Thing { [DataMember] public int Id { get; internal set; } [DataMember] public string Name { get; internal set; } // 其他属性 }
- 在包含
Thing类的程序集的AssemblyInfo.cs中,添加InternalsVisibleTo特性,授权ThingEditor模块访问内部成员:
[assembly: InternalsVisibleTo("ThingEditor")]
这样:
- ThingView模块:因为没有授权,只能访问
public的getter,无法修改属性。 - ThingEditor模块:可以直接访问
internal的setter,实现完全编辑。
优点是零额外代码,缺点是ThingView模块如果想通过反射还是能修改属性,但绝大多数业务场景下这个限制已经足够。
方案三:基类+派生类(适合嵌套结构复杂的场景)
如果你的嵌套结构特别复杂,接口方式的扩展性不够,可以用基类作为只读抽象:
- 创建一个只读基类,仅暴露
get属性:
public abstract class ReadOnlyThing { public abstract int Id { get; } public abstract string Name { get; } // 其他只读属性 }
- 让
Thing类继承基类,添加setter和DataContract标记:
[DataContract] internal class Thing : ReadOnlyThing { [DataMember] public override int Id { get; internal set; } [DataMember] public override string Name { get; internal set; } }
然后ThingView模块依赖ReadOnlyThing,ThingEditor模块依赖Thing类。这个方案的好处是嵌套类可以统一继承对应的只读基类,但缺点是C#单继承的限制会降低灵活性,不如接口方案通用。
总结
- 如果你需要严格的编译时权限控制,且类数量多:选方案一(接口+T4模板)。
- 如果你追求简单快速实现:选方案二(InternalsVisibleTo)。
- 如果你有复杂的嵌套继承结构:可以考虑方案三。
内容的提问来源于stack exchange,提问作者Frank Puffer

