如何在C#中创建一个可私有设置的只读结构(如Size)?

时间:2012-12-31 10:16:52

标签: c# struct const structure readonly

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

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 只读结构(如果这样的话甚至可能,我怀疑),我会给你答案。当然,这个问题实际上并不需要,但要知道是否可以做到这一点仍然是一件好事。

2 个答案:

答案 0 :(得分:5)

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

SomeSize.Width = 17.0;

您可以更改值并仍然可以进行编译的唯一方法是:

Size temp = SomeSize;
temp.Width = 17.0;

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

但是,如果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<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); }
}

答案 1 :(得分:0)

.net语言中结构类型的一个主要限制是,可写结构类型存储位置(变量,字段,参数,数组插槽等)的字段本身是可写存储位置(如果它们是是结构类型,它们的字段同样是可写存储位置),这种访问仅适用于结构类型存储位置,并且除了System.Array之外的任何类型都不能通过这种方式公开< em> property 就像存储位置一样。

如果类提供了一种方法,可以将该存储位置作为ref参数传递给提供的回调,则可以在受控环境下将私有存储位置公开为存储位置。< / p>

例如:

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参数{{1}到高度时,每次进入范围时都需要生成一个闭包来保存adder,并且每次执行方法调用时都需要生成一个委托;将数量作为HeightAdder传递参数避免了开销)。

类似于refList<T>的类可以使用类似的方法,以允许回调方法直接作用于列表槽或字典条目,如果访问方法包含参数索引或密钥。

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