12

我有一个Bar包含引用类型的私有字段的类Foo。我想Foo在公共财产中公开,但我不希望财产的消费者能够改变Foo......但是它应该可以通过内部改变Bar,即我不能制作该领域readonly

所以我想要的是:

      private _Foo;

      public Foo 
      { 
         get { return readonly _Foo; } 
      } 

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

4

8 回答 8

12

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

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

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

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

_Foo = new Foo(...);

或者

_Foo.SomeProperty = newValue;

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

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

于 2009-03-25T12:12:36.150 回答
3

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

于 2009-03-25T12:18:23.717 回答
2

正如您已经推测的那样,制作副本、aReadOnlyCollection或不可变通常是三种最佳途径。Foo

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

于 2009-03-25T12:14:03.667 回答
1

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

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

于 2009-03-25T12:16:19.637 回答
1

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

于 2009-03-25T12:40:42.383 回答
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; }}
}
于 2009-03-25T12:25:11.110 回答
0

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

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

例如,Foo是类型FooClass

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

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

因此,在您的情况下,您很高兴他们获得该Message属性,但不对其进行设置,并且您绝对对他们调用该方法不满意。

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

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

我们省略了 mutating 方法,只保留了属性的 getter。

然后将该接口添加到FooClass.

现在在你的类中Foo,你有一个字段:

private FooClass _foo;

还有一个属性获取器:

public IFooConst Foo
{
    get { return _foo; }
}

这基本上可以精确地手动复制 C++const关键字会自动执行的操作。在伪 C++ 术语中,类型的引用const Foo &就像一个自动生成的类型,它只包含那些Foo被标记为成员的const成员。将其转换为 C# 的一些理论上的未来版本,您将声明FooClass如下:

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

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

实际上,我所做的只是通过用新关键字标记一个安全成员,将信息合并IFooConst回。所以在某种程度上,添加一个 const 关键字除了对该模式的正式方法之外不会对语言增加太多。FooClassconst

然后,如果你有const一个对象的引用FooClass

const FooClass f = GetMeAFooClass();

您将只能调用 const 成员f

请注意,如果FooClass定义是公开的,则调用者可以将 anIFooConst转换为FooClass. 但他们也可以在 C++ 中做到这一点——这被称为“抛弃const”,并涉及一个名为const_cast<T>(const T &).

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

于 2009-03-25T13:01:04.927 回答
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 对您来说非常重要,那么这是一种替代方法。

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

于 2015-02-11T22:39:40.187 回答