C#如何禁止外部修改只读属性内部公共字段且不影响原类型复用
问题陈述
现有如下自定义向量类:
namespace StackoverflowQuestion1 { public class MyVector { public float x; public float y; public float z; public MyVector(float x, float y, float z) { this.x = x; this.y = y; this.z = z; } } }
定义了可移动对象的通用接口IMovable,代表对象位置可变更:
namespace StackoverflowQuestion1 { public interface IMovable { public string Name { get; } public MyVector Position { get; } } }
Furniture(家具)类属于可移动对象,因此实现了上述接口:
namespace StackoverflowQuestion1 { public class Furniture : IMovable { public string Name { get; private set; } public MyVector Position { get; private set; } public Furniture(string name, float x, float y, float z) { this.Name = name; this.Position = new MyVector(x, y, z); } } }
按照预期,外部无法访问Name属性的私有setter,也无法直接修改Position属性(其setter为私有)。但实际上外部可以直接访问Position的x/y/z公共字段并修改其值,如下代码所示:
using StackoverflowQuestion1; class Program { static void Main(string[] args) { Furniture F = new Furniture("Chair", 1f, 2f, 3f); F.Name = "Office chair"; // 符合预期,编译报错 F.Position = new MyVector(5f, 6f, 7f); // 符合预期,编译报错 F.Position.x = 5f; // 不符合预期,可正常修改 F.Position.y = 6f; // 不符合预期,可正常修改 F.Position.z = 7f; // 不符合预期,可正常修改 } }
提问
如何实现仅Furniture类的内部成员可修改自身位置,外部完全无法修改Furniture的位置值,同时不能将MyVector类的坐标字段设为私有,避免MyVector在其他场景下无法正常修改使用?
解决方案
下面提供两种符合要求的常用实现方案:
方案1:对外返回MyVector副本(兼容性最高,无需修改原有接口)
核心逻辑是Furniture内部私有存储真实的位置向量实例,对外暴露的Position属性每次返回全新的副本,外部修改副本完全不会影响内部的真实值,内部需要修改位置时直接操作私有字段即可。
修改后的Furniture代码如下:
namespace StackoverflowQuestion1 { public class Furniture : IMovable { public string Name { get; private set; } // 内部私有存储真实位置,外部不可访问 private MyVector _position; // 对外暴露的Position每次返回副本 public MyVector Position => new MyVector(_position.x, _position.y, _position.z); public Furniture(string name, float x, float y, float z) { this.Name = name; this._position = new MyVector(x, y, z); } // 示例:内部修改位置的方法 public void MoveInternal(float offsetX, float offsetY, float offsetZ) { _position.x += offsetX; _position.y += offsetY; _position.z += offsetZ; } } }
- 优点:完全不需要修改原有MyVector、IMovable的定义,兼容所有现有业务代码,MyVector在其他场景仍可正常修改字段使用。
- 缺点:每次访问Position都会生成新的对象实例,高频访问场景下会有轻微的GC开销。
方案2:新增只读向量接口(性能最优,无额外开销)
核心逻辑是新增仅暴露x/y/z读取能力的只读接口,让MyVector实现该接口,调整IMovable的Position返回只读接口,外部拿到只读接口无法修改字段,Furniture内部仍存储可写的MyVector实例自由修改。
实现步骤如下:
- 新增只读向量接口:
public interface IReadOnlyVector { float X { get; } float Y { get; } float Z { get; } }
- 修改MyVector实现该接口(不需要修改原有公共字段,不影响其他场景使用):
public class MyVector : IReadOnlyVector { public float x; public float y; public float z; public MyVector(float x, float y, float z) { this.x = x; this.y = y; this.z = z; } // 显式实现只读接口,不污染原有类的公共成员 float IReadOnlyVector.X => x; float IReadOnlyVector.Y => y; float IReadOnlyVector.Z => z; }
- 修改IMovable接口的Position返回类型:
public interface IMovable { public string Name { get; } public IReadOnlyVector Position { get; } }
- 修改Furniture实现:
public class Furniture : IMovable { public string Name { get; private set; } // 内部存储可写的MyVector private MyVector _position; // 对外返回只读接口 public IReadOnlyVector Position => _position; public Furniture(string name, float x, float y, float z) { this.Name = name; _position = new MyVector(x, y, z); } // 内部修改位置的示例方法 public void MoveInternal(float offsetX, float offsetY, float offsetZ) { _position.x += offsetX; _position.y += offsetY; _position.z += offsetZ; } }
- 优点:无额外对象创建开销,外部拿到IReadOnlyVector实例完全无法修改内部值,MyVector原有公共字段保留,其他场景可正常使用。
- 缺点:需要小幅调整原有IMovable接口的定义,已有的依赖IMovable的代码需要适配。
内容的提问来源于stack exchange,提问作者jkf
相关产品推荐
相关产品推荐

