【问题标题】:static immutable default instance静态不可变默认实例
【发布时间】:2011-03-06 15:48:22
【问题描述】:

我希望能够为可以使用的类提供默认值,但问题是如果它们被更改,那么它将影响对它的所有引用并且不会是“默认”。通过使用这样的默认值,它可以节省内存,并且允许默认值(如果需要)传播到所有使用默认值的引用。

一个简单的例子是

class A
{
    static public A Default;
}

然后可以使用 A.Default 作为 A 的“默认”实例。同样,问题是 A 不是不可变的或至少“冻结”,对它的更改将更改所有引用。如果这是人们想要的行为,这可能会很好,但如果意外更改默认值,则会造成严重破坏。

我真正需要的是一种深度冻结和解冻默认值的方法。

显然,一种方法是简单地将所有 setter 设置为一个条件并将集合标记为只读。提供如此简单的行为似乎需要大量重复工作。

是否有一个简单的库、模式或反射来完成这个?写入时复制功能会很好,因此如果尝试更改默认值,则会创建一个新的可变实例。不仅如此,如果有机会提高性能(更改大小),甚至可以创建享元实例。

示例:假设您创建了 1M 大(内存大小)对象,初始状态都相同。通过使用默认模式,这只会创建 1 个实际对象。假设您为所有状态(例如位置)更改 1 个参数,但对象本身非常大。使用享元模式,您只需更改 1M 参数来跟踪(像往常一样更慢但内存更少),而不是 1M 新对象。在更改了足够多的参数后,最终将完整的对象分配给它的引用。

有这样的吗?

【问题讨论】:

  • 在什么情况下,您想“解冻”默认值以及谁应该能够“解冻”默认值?
  • 你确定你真的需要这么复杂的东西吗?
  • 这似乎是一个设计缺陷。您有一个 A 类,当它是“默认”实例时它是不可变的,而当它不是时它是可变的。也许你需要两个类,一个是不可变的,一个是可变的,或者你的 A 类应该是一个结构体或值类型,比如对象(比如字符串)
  • @Merick:不,默认是“不可变的”,但 A 的实例不是。默认是 A(静态)类型的一部分。如果 Default 不是静态定义的,那么你是对的。
  • “它节省了内存”。 c2.com/cgi/wiki?PrematureOptimization

标签: c# .net oop


【解决方案1】:

我使用的一种可能的方法是实现一个只读接口并将静态默认返回类型更改为:

interface ISomeClass
{
    string MyProperty { get; }
}

class SomeClass : ISomeClass
{
    string MyProperty { get; set; }

    public static ISomeClass Default = new SomeClass(); 
}

然后您可以强制在任何时候都需要更改默认值,即可变引用 被明确要求,也许是通过单独的方法:

        public static SomeClass GetMutableDefault()
        {
            return Default as SomeClass;
        }

然后,您会在编译时检查任何试图更改 SomeClass 实例的方法都没有使用默认值,除非它明确说明。

【讨论】:

    【解决方案2】:

    没有类似的开箱即用。您需要创建自己的写时复制实例和代码。

    真正的写时复制行为要做的事情:

    • 创建一个小的“引用”对象,每次有人读取“Default”属性时都会返回它的一个新实例。此对象始终引用相同的(私有,根据定义为只读)内部数据。

    • 只要数据发生更改,而您仍然使用只读数据,请创建内部数据的副本并将其分配给您的参考对象。

    .NET Framework 设计者对一些具有类似要求的类采取了更明确的路线。例如,如果您查看CultureInfo,“默认”实例是只读的,如果您尝试修改这些实例,则会出现异常。但是,您可以轻松创建可变副本(其中一个构造函数接受另一个 CultureInfo 实例)。

    【讨论】:

    • 是的,但我不确定如何总体上有效地完成这样的事情。对于一个案例来说并不难,但如果你必须一直这样做,那就是一团糟。他们是如何实现他们的只读默认值的?查看帮助它说他们使用了包装器,但是这个包装器做了很长的路还是什么?
    • 如果您必须一直这样做,那么您的设计可能会出现某种错误。关于 MS 和 CultureInfo 等,据我所知,他们确实明确地实现了包装器。无论如何,总有可能使用使用模板生成样板代码的代码生成工具,这样您基本上只需编写一次模板,然后围绕它生成代码。或者您可以使用抽象类或接口并在运行时生成代码Reflection.Emit...有很多可能性。
    【解决方案3】:

    我想到了几种方法来做到这一点:

    1. 有一个名为IsReadOnly 的标志,这样您的所有修改器(可以更改实例的设置器和方法)都会在它为真时抛出异常。您的Default 实例将在IsReadOnly 设置为true 的情况下创建。

    2. 创建一个基类 (FooReadOnly),所有 mutators 都会在其中抛出异常,然后创建一个派生类 (Foo),mutators 可以在其中工作。您的 Default 实例的类型为 FooReadOnly

    【讨论】:

      【解决方案4】:

      您可以查看 DependancyObject(s) 和 DependanceProperty(s) 在 WPF/Silverlight 中的工作方式。

      这是一个示例,说明它如何在 WPF/Silverlight 中为具有属性“Foo”且默认值为 5 的类“A”工作。

      class A : DependancyObject {
        static DependancyProperty PropertyFoo = DependanceProperty.Register( "Foo", typeof(int), typeof(A), new PropertyMetadata( 5 ) );
      
        int Foo {
          get { return (int)GetValue( PropertyFoo ); }
          set { SetValue( PropertyFoo, value ); }
        }
      

      缺点是你必须“手动”实现你的属性,你不能利用简单的“int Foo { get; set; }”语法,但代码 sn-ps 可以帮助很多。

      显然,如果您不想使用 WPF 或 Silverlight,则必须自己实现所有这些,但您可以获得以下优势。

      由于 DependancyProperties 是对象,它们可以保存其默认值,该默认值可以由任何未覆盖该值的 DependancyObject 共享。

      DependancyObjects 仅在值更改时才保留值列表,因此与默认值相同的对象不会占用额外的内存。

      由于所有属性集都通过 DependancyObject.SetValue,因此很容易在一个地方实现逻辑,以使某些属性或整个对象只读。

      还有其他优点/功能可以添加,例如属性动画等,但如果您实现了它,您可以根据需要保持简单/复杂。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-11-10
        • 1970-01-01
        • 2014-02-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-03-28
        • 1970-01-01
        相关资源
        最近更新 更多