代码之家  ›  专栏  ›  技术社区  ›  Joel in Gö

如何将引用类型属性设置为“只读”

  •  12
  • Joel in Gö  · 技术社区  · 17 年前

    我有一节课 Bar 包含引用类型的私有字段 Foo . 我想曝光 在公共财产中,但我不希望财产的消费者能够改变 …但是,它应该可以通过以下方式进行内部修改: 酒吧 ,也就是说,我不能进入这个领域 readonly .

    所以我想要的是:

          private _Foo;
    
          public Foo 
          { 
             get { return readonly _Foo; } 
          } 
    

    …这当然是无效的。我可以退回一个克隆的 (假设是 IClonable 但这对消费者来说并不明显。我应该把房子的名字改成 FooCopy ??应该是一个 GetCopyOfFoo 改为方法?你认为最佳实践是什么?谢谢!

    8 回复  |  直到 9 年前
        1
  •  11
  •   Jon Skeet    17 年前

    听起来好像你是在从C++中得到“const”的等价物。这在C中不存在。没有办法表明消费者不能修改一个对象的属性,但是其他的东西可以(当然,假设变化的成员是公共的)。

    您可以按照建议返回foo的克隆,或者 看法 在比赛场地上,如 ReadOnlyCollection 对于集合。当然,如果你能 Foo 一种不变的类型,它会使生活更简单…

    注意,在 领域 只读并使对象本身不可变。

    目前,类型本身可以通过两种方式改变事情。它可以做到:

    _Foo = new Foo(...);
    

    _Foo.SomeProperty = newValue;
    

    如果它只需要能够执行第二个操作,那么该字段可以是只读的,但是您仍然存在这样的问题:人们获取属性时会改变对象。如果它只需要做第一件事,实际上 或者已经是不可变的,或者可以是不可变的,您只需提供一个只有“getter”的属性,就可以了。

    它是 非常重要 了解更改字段值(使其引用不同的实例)和更改字段所引用对象的内容之间的区别。

        2
  •  3
  •   Brian Rasmussen    17 年前

    不幸的是,目前在C中没有一个简单的方法可以解决这个问题。您可以提取的“只读部分” Foo 在接口中,并让属性返回 .

        3
  •  2
  •   John Feminella    17 年前

    复制,A ReadOnlyCollection 或拥有 Foo 不变通常是最好的三条路线,正如你已经推测的那样。

    有时,当我做任何比返回底层字段更重要的事情时,我都更喜欢方法而不是属性,这取决于所涉及的工作量。方法意味着正在发生一些事情,当消费者使用您的API时,它们会为他们升起更多的标志。

        4
  •  1
  •   jdmichal    17 年前

    “克隆”你接收和归还的foo对象是一种正常的做法,叫做 防御性复制 . 除非对克隆有一些用户看不见的副作用,否则绝对没有理由不这样做。它通常是唯一的方式来保护您的类的内部私有数据,特别是在C++或Java中,其中C++的思想是 const 不可用。(也就是说,必须这样做才能在这两种语言中正确地创建真正不可变的对象。)

    只是澄清一下,可能的副作用是用户(合理地)希望返回原始对象,或者foo持有的某些资源无法正确克隆。(在这种情况下,它在执行iClonable做什么?!)

        5
  •  1
  •   Jim Arnold    17 年前

    如果你不想让任何人扰乱你的国家……不要暴露它!正如其他人所说,如果有什么东西需要查看您的内部状态,请提供它的不变表示。或者,让客户告诉 做点什么(谷歌的“告诉不要问”),而不是自己做。

        6
  •  0
  •   RossFabricant    17 年前

    为了澄清jon skeet的评论,您可以创建一个视图,它是可变foo的不可变包装类。下面是一个例子:

    class Foo{
     public string A{get; set;}
     public string B{get; set;}
     //...
    }
    
    class ReadOnlyFoo{
      Foo foo; 
      public string A { get { return foo.A; }}
      public string B { get { return foo.B; }}
    }
    
        7
  •  0
  •   Daniel Earwicker    17 年前

    实际上,你可以复制C++中的C++ const的行为——你只需要手动完成。

    无论什么 Foo 是的,调用者修改其状态的唯一方法是通过对其调用方法或设置属性。

    例如, 属于类型 FooClass :

    class FooClass
    {
        public void MutateMyStateYouBadBoy() { ... }
    
        public string Message
        {
            get { ... }
            set { ... }
        }
    }
    

    所以在你的情况下,你很高兴他们得到 Message 属性,但不设置它,并且您肯定不喜欢它们调用该方法。

    因此,定义一个接口来描述他们可以做什么:

    interface IFooConst
    {
        public string Message
        {
            get { ... }
        }
    }
    

    我们省略了变异方法,只留下了属性的getter。

    然后将该接口添加到 足类 .

    现在在你班上 属性,您有一个字段:

    private FooClass _foo;
    

    和一个地产经纪人:

    public IFooConst Foo
    {
        get { return _foo; }
    }
    

    这基本上是手工复制的C++ const 关键字将自动执行。在psuedo-c++术语中,类型为 const Foo & 就像一个自动生成的类型,它只包含 标记为 康斯特 成员。把它翻译成未来理论版本的C,你会声明 足类 这样地:

    class FooClass
    {
        public void MutateMyStateYouBadBoy() { ... }
    
        public string Message
        {
            get const { ... }
            set { ... }
        }
    }
    

    实际上,我所做的就是将信息合并到 IFooConst 回到 足类 ,将一个安全成员标记为新成员 康斯特 关键字。因此,在某种程度上,添加一个const关键字除了对这个模式进行形式化的处理外,不会给语言增加太多内容。

    如果你有一个 康斯特 参考A 足类 对象:

    const FooClass f = GetMeAFooClass();
    

    你只能打电话给警察 f .

    注意,如果 足类 定义是公共的,调用方可以 伊夫康斯特 变成一个 足类 . 但是他们也可以在C++中这样做——它被称为“铸造”。 康斯特 “并且涉及到一个特殊的接线员 const_cast<T>(const T &) .

    还有一个问题是,在您的产品版本之间,接口的发展不是很容易的。如果第三方可以实现您定义的接口(如果他们可以看到,他们可以自由地做),那么在未来的版本中,您不能向它添加新的方法,而不需要其他人重新编译他们的代码。但是,如果您正在编写一个可扩展库供其他人构建,那么这只是一个问题。可能是内置的 康斯特 这个特性可以解决这个问题。

        8
  •  0
  •   Adas Lesniak    11 年前

    我在考虑类似的安全问题。可能有办法。很清楚,但不短。总的想法很简单。但是我总是找到一些方法,所以从来没有测试过它。但你可以检查一下-也许对你有用。

    这是伪代码,但我希望它背后的想法是清楚的

    public delegate void OnlyRuller(string s1, string s2);
    public delegate void RullerCoronation(OnlyRuller d);
    
    class Foo {
      private Foo();
      public Foo(RullerCoronation followMyOrders) {
         followMyOrders(SetMe);
      }
    
      private SetMe(string whatToSet, string whitWhatValue) {
         //lot of unclear but private code
      }
    }
    

    所以在创建这个属性的类中,您可以访问setme方法,但它仍然是私有的,所以除了creator foo之外,它看起来是不可更改的。

    尽管如此,对于任何比少数属性更大的属性,这可能很快就会变得非常混乱——这就是为什么我总是喜欢其他封装方式的原因。但是,如果您不允许客户更改foo对您来说非常重要,那么这是一种选择。

    然而,正如我所说,这只是理论。

    推荐文章