【问题标题】:C#, correct use of the static keyword when designing a basic color classC#,在设计基本颜色类时正确使用static关键字
【发布时间】:2008-11-22 15:41:03
【问题描述】:

我正在制作一个 Color 类,并提供一个标准构造函数,例如

Color(int red, int green, int blue)

然后我想提供一种简单的方法来获取最常见的颜色,例如 颜色.蓝色,颜色.红色。我看到了两种可能的选择:

public static readonly Color Red = new Color(255, 0, 0);

public static Color Red { get { return new Color(255, 0, 0); } }

我不完全了解的是,两者之间是否存在优势,以及 static 关键字的工作原理。我的想法是:首先创建一个实例,然后该实例在程序的整个过程中都保留在内存中,并且每次调用 Red 时,都会使用该实例。后者只在第一次使用时创建一些东西,但每次都会创建一个新实例。如果这是正确的,那么我会争辩说,如果我提供了很多预定义的颜色,那么第一个会使用很多不必要的内存吗?因此,每次我猜测时,它都是内存使用与实例化对象的运行时开销。

我的推理正确吗?在设计类和使用 static 关键字时,任何关于最佳实践的建议都会很棒。

【问题讨论】:

    标签: c# static


    【解决方案1】:

    我猜你可能已经知道框架提供了一个Color 结构。我猜你正在创建一个 Color 类只是为了练习。

    您对 static 关键字的含义表示不确定,尽管您已正确使用它。当static 应用于类或结构的成员时,意味着该成员作为一个整体属于该类,并且不适用于单个实例。静态数据成员(字段)只创建一次;实例没有自己的副本。在没有实例引用的情况下调用静态函数(方法和属性)。

    就内存使用而言,在您的情况下我不会太担心。您的Color 类不应在每个实例中使用超过几个字节(例如,框架的Color 结构将红色、绿色、蓝色和alpha 存储在一个32 位int 中。)。如果你的Color 是一个真正的class 而不是struct,那么你将有更多的字节开销(每个实例将有一个额外的 32 位 v-table/typeinfo 指针,并且每个引用是一个额外的 32 位),但即便如此,你说的是每个实例大约 12 个字节。如果您预定义了 100 种不同的颜色,您将使用

    不过,延迟实例化是有原因的。有些类确实使用大量内存,有些会占用有限的系统资源,有些则需要很长时间才能构建自己,等等。对于这些类,有时最好使用像这样的模式:

    class Heavy{
        static Heavy first;
        static Heavy second;
    
        public static Heavy First{
            get{
                if(first == null)
                    first = new Heavy();
                return first;
            }
        }
        public static Heavy Second{
            get{
                if(second == null)
                    second = new Heavy();
                return second;
            }
        }
    }
    

    另一个考虑因素是可变性。您的 Color 类是可变的还是不可变的?换句话说,您的类的实例是否可以更改其值,或者它们是否总是在创建后代表相同的值?

    如果您的Color 是可变的,那么拥有静态“红色”访问器的唯一正确方法是您的第二个示例,即每次访问都创建一个新访问器。这样一来,某人就不能做类似的事情:

    Color.Red.G = 255;
    

    并使单个共享的 Color.Red 实例实际上代表黄色。

    但也请记住,在以下情况下:

    for(int y = 0; y < bmp.Height; y++)
    for(int x = 0; x < bmp.Width; x++)
        if(bmp.GetPixel(x, y) == Color.Red))
            MessageBox.Show("Found a red pixel!");
    

    将创建您的Color 类的很多 个实例。当然,它们稍后会被垃圾收集,但这仍然是您上面的第一个构造的案例参数(或我给出的“重”示例)。

    现在,如果您的 Color 实际上是一个结构,那么这是一个略有不同的故事。当你 new 一个结构时没有堆分配,也没有 v-table 或引用指针,所以真正考虑的是你的构造函数需要多长时间。

    【讨论】:

    • 优秀的答案!删除我的,赞成这个。
    • 这是一个非常有用的答案,帮助我理解了更多使用静态的想法。谢谢!
    【解决方案2】:

    您对权衡的推理和识别似乎是正确的。如果您查看 System.Drawing.Color 结构,您会发现它使用了第二种技术。初始化新结构的开销很小,因此这可能比预先创建大量已知颜色要好。

    我希望 Color 是一个不可变的结构。但是,如果您打算创建一个类,那么如果您决定使用第一种技术,则需要确保它是不可变的。

    【讨论】:

    • 我会评论而不是复制这里所说的内容。如果您不能使用 System.Drawing 或 System.Windows.Media,那么模仿他们的设计可能是最好的方法。对于一些简单的事情,你应该使用一个结构。
    【解决方案3】:

    关于使用静态的一个注意事项是,您应该确保不要滥用它来执行诸如存储全局数据之类的事情。

    如果您确实需要至少在 WPF 中的全局数据,模式是拥有一个引用其自身的非静态实例的静态属性。例如Application.Current。这是我为我的应用程序复制的模式。

    【讨论】:

      【解决方案4】:

      正如你所说,第一个只会创建一个颜色实例:

      public static readonly Color RED = new Color(255, 0, 0);
      

      在幕后,我相信在运行时它只会在第一次被调用时被实例化。但是我还没有在调试器中自己检查过。

      正如您所说的静态 getter 替代方案,您提供的实现将在您调用它时不必要地填满堆。原因是每次调用都会创建一个new对象。

      如果您真的想选择 getter 替代方案但又不想填满堆,那么至少也要使 Color 类的创建也成为静态的。像这样:

      private static readonly Color RED = new Color(255, 0, 0);
          // RED is created once when it is invoked for the first time.
      
      public static Color Red { 
          get { 
              return RED; 
                  // Will return a created RED object or create one 
                  // for the first time.
          } 
      }
      

      【讨论】:

      • 静态初始化器在第一次引用 class 时运行,而不是第一次引用 field 时。访问 Color.Red,如果是第一次使用 Color 类,也会初始化 Color.Green、Color.Yellow 等。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-25
      相关资源
      最近更新 更多