这是C#类型系统无法解决的问题。但是通常来说不那么追求洁癖的话也没什么太大的关系。
C#的友元只能到程序集的层面,没有类型层面上的友元。通常这种问题用internal interface解决(如果有继承关系也可以用protected interface),当然这个也没有办法细化到类型的级别,但一般够用。
顺便说一下什么叫做internal interface,这个要借助C#的一个特性,叫做显示接口实现。
显示接口实现有一个非常有用的特性,就是显示接口实现的成员其可见性等价于接口。这就带来一种手法:
internal interface IFoo { void SetName( string name ); } public class MyClass : IFoo { public Name { get; private set; } void IFoo.SetName( string name ) => Name = name; }
这样就使得,任何可以看到IFoo接口的类型,都可以调用SetName方法,而任何看不到IFoo接口的类型,都不能调用。
当然有人会说,为啥要这么麻烦?我直接写成这样不就好了?
public Name { get; internal set; }
这样的确可以,但是显示接口实现的好处在于更好的语义提示:
public void ResetName( MyClass instance) => ((IFoo) instance).SetName( "" );
这里的强转可以起到语义提示,我要干坏事了……
当然,未来C#里面会增加扩展属性(和扩展方法一样),到时候可以在很大程度上解决这个问题。当然不嫌丑的话,也可以用扩展方法解决。
要实现这一点,我们就必须把Equipment从Human剥离出来。然后用一个EquipmentContainer来管理这一个东西,很显然,当Equipment从Human剥离出来后,他的set的问题就被彻底解决了。因为很显然的,Human并没有设置Equipment,它本质上是被EquipAction所使用的。
在使用扩展方法的模式下,这会使得代码变得丑陋:
internal sealed class EquipmentContainer { public IEquipment Equipment{ get; set; } } public static class EquipmentExtensions { public static IEquipment GetEquipment( Human human ) { return human.Features.Get<EquipmentContainer>().Equipment;// Features参考AspNetCore的设计。 } internal static void SetEquipment( Human human, IEquipment equipment ) { human.Features.Get<EquipmentContainer>().Equipment = equipment; } }
顺便说一下这个EquipmentContainer的意义在哪里,它的意义就在于,当你看不到这个类型的时候,你永远也没法给Human设置Equipment,如果我们不用这样一个中介而直接使用IEquipment,则因为Features方法是public的,导致任何人都可以设置Equipment