3

不确定这是否可以做到,但我需要计算一个大小并将其存储在一个基类中,然后将该结果作为只读方式公开给子类。将 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 返回一个真正的只读结构(如果这样的事情甚至可能,我对此表示怀疑),我会给你答案。当然,这个问题实际上并不需要这样做,但是知道它是否可以完成仍然是一件好事。

4

2 回答 2

5

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

SomeSize.Width = 17.0;

您可以更改值并仍然使其编译的唯一方法是这样的:

Size temp = SomeSize;
temp.Width = 17.0;

但是,就像我说的那样,由于这只是值的副本,它实际上不会改变SomeSize属性的值——它只会改变temp.

但是,如果Size类型是一个类,您仍然可以通过简单地返回对象的克隆而不是对原始对象的引用来实现相同的保护。例如, if 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<T>对象。相反,它返回一个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); }
}
于 2012-12-31T10:36:45.033 回答
0

.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委托已将范围,并且每次执行方法调用时都需要生成一个委托;将数量作为参数传递可以避免这种开销)。refadderHeightAdderref

如果访问方法包括索引或键的参数,则类似的方法可以由类似于List<T>Dictionary<TKey,TValue>允许回调方法直接作用于列表槽或字典条目的类使用。

这种方法的一个很好的特点是它允许集合提供并发访问的样式,否则这些访问是很困难的。如果集合中的事物属于可与Interlocked方法一起使用的类型(或者是其字段可与此类方法一起使用的公开结构类型),则客户端代码可以使用此类方法对底层数据执行线程安全的原子操作。如果集合中的东西不是这种类型,那么集合可能能够比其他方法更安全地实现锁定。例如, aConcurrentIndexedSet<T>可能有一个ActOnItem(int item, int timeout, ActByRef<T> proc); 将在锁内的项目上调用的方法proc(相信客户端提供proc可以信任在合理的时间范围内返回)。虽然这样的代码无法防止出现proc死锁或以其他方式被搁置的可能性,但它可以确保在将控制权返回给调用代码之前释放锁。

于 2013-01-01T18:55:59.290 回答