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

本文关键字:设置 只读 Size 结构 一个 创建 | 更新日期: 2023-09-27 17:50:27

不确定是否可以做到这一点,但我需要计算一个大小并将其存储在基类中,然后将结果以只读方式公开给子类。通过将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只读结构(如果这样的事情是可能的,我对此表示怀疑(,我会给你答案。诚然,这个问题实际上并不需要这样做,但如果能做到这一点,那仍然是一件好事。

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

您一定没有尝试编译它,因为您提出的内容已经满足了您的需求。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<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); }
}

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

如果一个类提供了一个方法,可以将私有存储位置作为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<T>Dictionary<TKey,TValue>的类可以使用类似的方法,以允许回调方法直接作用于列表槽或字典条目,如果访问方法包括索引或关键字的参数。

这种方法的一个很好的特性是,它允许集合提供在其他方面很困难的并发访问样式。如果集合中的东西属于可以与Interlocked方法一起使用的类型(或者是其字段可与此类方法一起使用(,则客户端代码可以使用此类方法对底层数据执行线程安全原子操作。如果集合中的东西不是这样的类型,那么该集合可能能够比其他方法更安全地实现锁定。例如,ConcurrentIndexedSet<T>可能具有ActOnItem(int item, int timeout, ActByRef<T> proc)家族;将对锁内的项调用CCD_ 28的方法(相信客户端提供的CCD_。虽然这样的代码不能防止proc死锁或以其他方式被阻塞的可能性,但它可以确保在将控制权返回给调用代码之前释放锁。