【问题标题】:How can I create a privately-settable read-only struct (such as Size) in C#?如何在 C# 中创建私有可设置的只读结构(例如 Size)?
【发布时间】:2012-12-15 12:53:19
【问题描述】:

不确定这是否可以做到,但我需要计算一个大小并将其存储在一个基类中,然后将该结果作为只读方式公开给子类。将 Size 本身设置为只读很容易,只需将其隐藏在具有受保护的 getter 和私有 setter 的属性后面,就像这样......

private Size _someSize;
protected Size SomeSize
{
    get{ return _someSize; }
    private set{ _someSize = value; }
}

然后在基类中,我可以这样设置...

SomeSize = new Size(23.0, 14.7);

...但是我不能从子类中这样做,因为 setter 是基类私有的。

不过,我也不希望子类能够修改 Size 结构的成员。

更新

在子类中,如果你尝试编译这个......

SomeSize.Width = 17.0;

...您将收到错误消息“无法修改 SomeSize 的返回值,因为它不是变量”,因此它确实像我希望的那样保护了基类中的值。

不过,如果有人能弄清楚如何让 getter 返回一个 true 只读结构(如果这样的事情是可能的,我对此表示怀疑),我会给你答案.当然,这个问题实际上并不需要这样做,但知道它是否可以完成仍然是一件好事。

【问题讨论】:

  • 类Size你也实现了吗?
  • 也许使用base.new(基类构造函数)(不知道c#语法到底...)
  • 没有。它是 .NET 框架的一部分。我知道我可以用 'readonly' 属性标记一个变量来做到这一点,但是我不能用它作为 getter/setter 的支持,因为我不能改变它。
  • 你是只在构造函数中设置值,还是可以通过方法调用来设置/更新?
  • 你测试了吗? Size 是一个结构(值类型),而不是一个类(引用类型),因此属性 getter 应该返回内部大小的副本,而不是对它的引用。因此,当子类更改属性返回的大小的 Width 属性时,它不应该触及私有 _someSize。除非我错了......

标签: c# struct constants structure readonly


【解决方案1】:

您一定没有尝试编译它,因为您提出的建议已经满足您的需求。 Size 类型是 structure(值类型),而不是 class(引用类型),因此属性 getter 将返回存储在 _someSize 中的值的副本,而不是对它的引用。因此,如果子类实际上试图更改 SomeSize.Width 属性,它实际上不会触及私有 _someSize 变量。它只是更改返回的值的副本。然而,编译器承认这样做是无效的,因此,它甚至不会让下面的行编译:

SomeSize.Width = 17.0;

你可以改变这个值并且仍然让它编译的唯一方法是这样的:

Size temp = SomeSize;
temp.Width = 17.0;

但是,就像我说的那样,因为这只是值的副本,它实际上不会更改 SomeSize 属性的值——它只会更改 temp 的值。

但是,如果Size 类型是一个类,您仍然可以通过简单地返回对象的克隆而不是对原始对象的引用来实现相同的保护。例如,如果Size,实际上是一个看起来像这样的类:

public class MySize
{
    public MySize(float height, float width)
    {
        Height = height;
        Width = width;
    }

    public float Height { get; set; }
    public float Width { get; set; }

    public MySize GetCopy()
    {
        return (MySize)MemberwiseClone();
    }
}

即使它的属性不是只读的,您仍然可以像这样从中创建一个伪只读属性:

private MySize _someSize;
protected MySize SomeSize
{
    get { return _someSize.GetCopy(); }
    private set { _someSize = value; }
}

但是,如果您真的希望返回对象的属性是只读的,那么唯一的方法就是实现您自己的原始类型的只读版本。例如,List<T> 类型支持获取自身只读版本的能力,以便您可以将其用于只读列表属性。因为List<T> 具有该内置功能,您可以轻松地执行以下操作:

private List<string> _list = new List<string>();
public ReadOnlyCollection<string> List
{
    get { return _list.AsReadOnly(); }
}

但是,如您所见,AsReadOnly 方法不会返回 List&lt;T&gt; 对象。相反,它返回一个ReadOnlyCollection 对象,这是一个全新的类型,被定制为列表的只读版本。因此,换句话说,真正创建只读Size 属性的唯一方法是创建自己的ReadOnlySize 类型,如下所示:

public struct ReadOnlySize
{
    public ReadOnlySize(Size size)
    {
        _size = size;
    }

    private Size _size;

    public float Height 
    {
        get { return  _size.Height; } 
    }

    public float Width
    {
        get { return _size.Width; }
    }
}

然后你可以像这样设置你的只读属性:

private Size _someSize;
public ReadOnlySize SomeSize
{
    get { return new ReadOnlySize(_someSize); }
}

【讨论】:

  • 正是我刚刚在更新中所说的,但既然你把它放在答案中,我就把它给你。另外@Steven,我建议不要使用“尖刻”的言论。 (即'显然......')人们会犯错误,在我的代码中,我在返回值被存储后弄乱了它,并试图将其设为只读或常量。我没有考虑过有人试图直接在 getter 上设置它的影响/保护,编译器阻止了它,从而给了我最初寻求的东西,只是来自不同的向量。
  • 对不起。老实说,我根本没有刻薄的意思,但我现在明白你会如何接受它。希望我编辑的答案现在更准确地传达了我的预期含义。我已经对你的问题投了赞成票,因为我认为这是一个好问题。
  • 不适用。都很好! :) 顺便说一句,我(暂时)删除了“接受状态”,因为从技术上讲这并没有给我我希望的 const/readonly-ness,尽管它确实解决了我们的特殊需求。不过,我想看看函数是否有任何方法可以返回真正的只读结构,所以我希望它出现在“未回答”类别中。如果没有人提出解决方案,或者证明没有解决方案,我将重新接受这一点。只是想让你知道发生了什么以及我为什么要删除它。
  • 只是从上面复制我的评论:如果我使用 readonly 属性定义一个 Size 类型的名为 'foo' 的变量,我可以在构造函数中设置 'foo',但不能在之后设置。这就明白了。但是,除了变量是只读的之外,结构的成员现在也是只读的。编译器不会让你设置宽度。该变量及其成员有一些保护。我试图查看保护是在结构的地址上,还是在结构本身上可以传递的东西上。 (我认为是前者,这意味着我的问题实际上是无效的。)
  • 编译器确实识别出该错误并阻止您更改值类型的只读属性的子属性之一,但它不会阻止您这样做属于引用类型的属性。例如,如果您将其更改为 readonly MySize 属性,那么它将允许您在构造函数之外更改高度或宽度。
【解决方案2】:

.net 语言中结构类型的主要限制之一是,虽然可写结构类型存储位置(变量、字段、参数、数组槽等)的字段本身是可写存储位置(如果它们是结构类型,它们的字段同样是可写的存储位置),这种访问仅适用于结构类型存储位置,并且除了System.Array之外的任何类型都无法公开属性,就像一个存储位置。

如果一个类提供了一种方法,该方法可以将该存储位置作为ref 参数传递给提供的回调,则该类可以将私有存储位置公开为受控情况下的存储位置。

例如:

public delegate void ActByRef<T1>(ref T1 p1);
public delegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2);
public delegate void ActByRef<T1,T2,T3>(ref T1 p1, ref T2 p2, ref T3 p3);

public void ActOnSize(ActByRef proc) 
  { proc( ref _someSize); }
public void ActOnSize<XT1>(ActByRef proc, ref XT1 xp1) 
  { proc( ref _someSize, ref xp1); }
public void ActOnSize<XT1,XT2>(ActByRef proc, ref XT1 xp1, ref XT2 xp2)
  { proc( ref _someSize, ref xp1, ref xp2); }

如果想在暴露了这种方法的东西的宽度上增加 5,那么可以使用

thing.ActOnSize((ref Size sz) => sz.Width += 5);

如果有一个局部变量“HeightAdder”,并且希望将其添加到对象的高度,可以使用

thing.ActOnSize((ref Size sz, ref int adder) =>
  sz.Height += adder, ref HeightAdder);

请注意,由于编写的 lambda 表达式不会捕获任何局部变量,因此可以将其作为静态委托进行评估(如果委托已将 HeightAdder,而不是 ref 参数 adder 添加到高度,则每次进入作用域时都需要生成一个闭包来保存HeightAdder,并且每次执行方法调用时都需要生成一个委托;将数量作为ref 参数传递可以避免这种开销)。

如果访问方法包含索引或键的参数,类似List&lt;T&gt;Dictionary&lt;TKey,TValue&gt; 的类可以使用类似的方法来允许回调方法直接作用于列表槽或字典条目。

这种方法的一个很好的特点是它允许集合提供并发访问的样式,而这在其他情况下是很困难的。如果集合中的事物属于可以与Interlocked 方法一起使用的类型(或者是其字段可用于此类方法的公开结构类型),则客户端代码可以使用此类方法在基础数据。如果集合中的事物不是这种类型,那么集合可能能够比其他方法更安全地实现锁定。例如,ConcurrentIndexedSet&lt;T&gt; 可能有一个 ActOnItem(int item, int timeout, ActByRef&lt;T&gt; proc) 家族;将在锁内的项目上调用proc 的方法(相信客户端提供的proc 可以被信任在合理的时间范围内返回)。虽然此类代码无法防止 proc 陷入死锁或以其他方式被搁置的可能性,但它可以确保在将控制权返回给调用代码之前释放锁。

【讨论】:

  • 如果我用 readonly 属性定义一个 Size 类型的名为 'foo' 的变量,我可以在构造函数中设置 'foo',但不能在之后设置。这就明白了。但是,除了变量是只读的之外,结构的成员现在也是只读的。编译器不会让你设置宽度。该变量 它的成员有一些保护。我试图查看保护是在结构的地址上,还是在结构本身上可以传递的东西上。 (我认为是前者,这意味着我的问题实际上是无效的。)
  • 如果结构仅作为只读属性公开,则将没有机制可以更改结构的字段。请注意,结构类型本身不必是“不可变的”来实现这一点;在相同情况下,具有暴露字段的结构同样不可修改。另请注意,如果结构封装了一个或多个它所引用的外部对象的状态,则这些对象的状态(以及结构的有效状态)可能会发生变化。
  • 这一切都归结为我认为的同一件事:没有办法从函数返回只读结构。需要明确的是,我不是在谈论返回相同的实例与副本。我是说无论返回哪个结构,都无法在退出时将其标记为只读。
  • @MarqueIV: Any 函数返回的结构将是只读的,除非或直到它的值存储在其他地方。如果值存储在可变存储位置,它将像所有存储在可变存储位置的结构一样是可变的,但这种突变只会影响存储在该可变位置的实例,不会影响存储的任何实例宇宙中的任何其他地方。
  • 实际上,您的评论“任何返回的结构......将是只读的”在技术上并不准确,因为结构本身可变的。编译器只是阻止你对它的访问,因为每个 getter 调用都会返回一个新实例,所以如果没有那个块,如果你直接在 getter 上设置宽度,然后是高度,你实际上是在两个不同的副本上设置它,你都不是之后可以访问。但是类型本身不是只读的。总而言之,据我所知,只有一个变量可以作为编译器功能只读,而不是它指向自身的结构。尽管如此,学习这个还是很有趣的。
猜你喜欢
  • 2018-06-27
  • 1970-01-01
  • 2015-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多