如何使引用类型属性“只读”

时间:2009-03-25 12:09:36

标签: c# properties encapsulation readonly

我有一个类Bar,其中包含一个包含引用类型Foo的私有字段。我想在公共财产中公开Foo,但我不希望财产的消费者能够改变Foo ......但是它应该由Bar内部改变。 ,即我无法创建字段readonly

所以我想要的是:

      private _Foo;

      public Foo 
      { 
         get { return readonly _Foo; } 
      } 

......这当然无效。我可以返回Foo的克隆(假设它是IClonable),但这对消费者来说并不明显。我应该将属性的名称更改为FooCopy ??它应该是GetCopyOfFoo方法吗?你认为最佳做法是什么?谢谢!

8 个答案:

答案 0 :(得分:11)

这听起来像是在等待C ++的“const”之后。这在C#中不存在。没有办法表明消费者不能修改对象的属性,但其他东西可以(假设变异成员是公共的,当然)。

您可以按照建议将Foo的克隆返回,或者可以将视图返回到Foo上,如ReadOnlyCollection对集合所做的那样。当然,如果你能使Foo成为一个不可变的类型,那会让生活更简单......

请注意,在字段只读和使对象本身不可变之间存在很大差异。

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

_Foo = new Foo(...);

_Foo.SomeProperty = newValue;

如果它只需要能够做第二个,那么该字段可能是只读的,但你仍然有人提取属性能够改变对象的问题。如果它只需要做第一个,并且实际上Foo已经是不可变的或者可以变成不可变的,你可以只提供一个只有“getter”的属性,你就可以了。

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

答案 1 :(得分:3)

不幸的是,目前在C#中没有简单的方法。您可以在界面中提取Foo的“只读部分”,并让您的属性返回而不是Foo

答案 2 :(得分:2)

制作副本,ReadOnlyCollectionFoo是不可变的通常是三条最佳路线,正如您已经推测的那样。

每当我做一些比简单地返回基础字段更重要的事情时,我有时会喜欢方法而不是属性,具体取决于涉及的工作量。方法意味着当他们使用您的API时,某些事情正在发生并为消费者提供更多的旗帜。

答案 3 :(得分:1)

“克隆”您收到的Foo对象并将其退回是一种称为防御性复制的常规做法。除非对用户可见的克隆存在一些看不见的副作用,否则绝对没有理由不这样做。它通常是保护类的内部私有数据的唯一方法,尤其是在C#或Java中,const的C ++概念不可用。 (IE,必须这样做才能在这两种语言中正确创建真正不可变的对象。)

只是为了澄清,可能的副作用可能是你的用户(合理地)期望返回原始对象,或者Foo持有的某些资源无法正确克隆。 (在这种情况下,它在实现IClonable的目的是什么?!)

答案 4 :(得分:1)

如果你不想让任何人搞乱你的状态......不要暴露它!正如其他人所说,如果某些东西需要查看你的内部状态,请提供它的不可变表示。或者,让客户告诉做某事(Google为“告诉不要问”),而不是自己做。

答案 5 :(得分:0)

为了澄清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; }}
}

答案 6 :(得分:0)

你实际上可以在C#中重现C ++ const的行为 - 你只需要手动完成它。

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

例如,Foo的类型为FooClass

class FooClass
{
    public void MutateMyStateYouBadBoy() { ... }

    public string Message
    {
        get { ... }
        set { ... }
    }
}

所以在你的情况下,你很高兴他们获得Message属性,但没有设置它,你肯定不满意他们调用那种方法。

因此,定义一个描述允许他们做什么的界面:

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

我们遗漏了变异方法,只留在了属性的getter中。

然后将该接口添加到FooClass的基本列表。

现在,在您的班级中使用Foo属性,您有一个字段:

private FooClass _foo;

属性获取者:

public IFooConst Foo
{
    get { return _foo; }
}

这基本上可以手动重现C ++ const关键字自动执行的操作。在psuedo-C ++术语中,类型const Foo &的引用类似于自动生成的类型,仅包含标记为Foo成员的const成员。将其转换为C#的理论未来版本,您可以像这样声明FooClass

class FooClass
{
    public void MutateMyStateYouBadBoy() { ... }

    public string Message
    {
        get const { ... }
        set { ... }
    }
}

我所做的全部工作是将IFooConst中的信息合并回FooClass,方法是使用新的const关键字标记一个安全成员。所以在某种程度上,除了正式的方法之外,添加一个const关键字不会对语言增加太多。

然后,如果您对const对象有FooClass引用:

const FooClass f = GetMeAFooClass();

您只能在f上调用const成员。

请注意,如果FooClass定义是公开的,则调用方可以将IFooConst转换为FooClass。但他们也可以在C ++中做到这一点 - 它被称为“抛弃const”并涉及一个名为const_cast<T>(const T &)的特殊运算符。

还有一个问题是接口在产品版本之间不易发展。如果第三方可以实现您定义的接口(如果他们可以看到它们可以自由执行),那么在将来的版本中无法向其添加新方法,而无需其他人重新编译其代码。但是,如果您正在为其他人构建可扩展的库,那么这只是一个问题。也许内置的const功能可以解决这个问题。

答案 7 :(得分:0)

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

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

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方法,但它仍然是私有的,所以除了创建者Foo看起来不可变。

对于任何大于几个属性的东西,这可能很快变得非常混乱 - 这就是为什么我总是喜欢其他封装方式。但是,如果不允许客户更改Foo对您来说非常重要,那么这是另一种选择。

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