在C#中使用结构体创建单例模式

3

这不是一个优化问题或其他任何问题。它本质上是一个“到底发生了什么”的问题。单例模式用于多次使用同一对象的单个实例。这很好,但如果我尝试使用结构体进行类似的模式,则不会得到单个实例。

我通过尝试对System.Drawing中的Color结构进行某些操作而遇到了这个问题。以下是一些示例代码:

    class Colors
    {
        private static Color _red;
        public static Color Red
        {
            get
            {
                if (_red.IsEmpty)
                    _red = Color.FromArgb(0xFF, 0xFF, 0x42, 0x39);

                return _red;
            }
        }
    }

    static void Main(string[] args)
    {
        var redOne = Colors.Red;
        var redTwo = Colors.Red;

        Console.WriteLine("redOne.Equals(redTwo) : {0}", redOne.Equals(redTwo));
        Console.WriteLine("redOne == redTwo : {0}", redOne == redTwo);
        Console.WriteLine("Object.Equals(redOne, redTwo) : {0}", Object.Equals(redOne, redTwo));
        Console.WriteLine("Object.ReferenceEquals(redOne, redTwo) : {0}", Object.ReferenceEquals(redOne, redTwo));
        Console.ReadLine();
    }

这个的输出结果是:
redOne.Equals(redTwo) : True
redone == redTwo : True
Object.Equals(redOne, redTwo) : True
Object.ReferenceEquals(redOne, redTwo) : False

前三个结果符合预期,但最后一个让我感到惊讶。现在,我最好的猜测是,当从Colors.Red返回_red时,它返回一个副本,就像普通值类型一样。 因此,虽然只有一个_red实例,但Colors.Red返回一个全新的实例,并将其存储到redOne和redTwo中。我的想法正确吗?
另外,如果这是正确的,请问在结构体上使用static关键字是否有任何意义?
谢谢

使用静态关键字在结构体上有意义吗?-- 在您代码的上下文中,static基本上意味着颜色红色对于Color类的所有实例都是相同的颜色。 - Robert Harvey
3个回答

7

ReferenceEquals无法处理结构体/值类型,只能处理类。

在两个结构体上调用ReferenceEquals会将每个结构体“装箱”到其自己的内存位置中,然后比较两个(显然)不同的地址。

由于类已经是引用类型,对ReferenceEquals的调用比较实例的实际地址。

基本上,您不需要存储“Red”的静态副本。 将代码更改为以下内容:

class Colors
{
    public static Color Red
    {
        get
        {
            return Color.FromArgb(0xFF, 0xFF, 0x42, 0x39);
        }
    }
}

额外阅读: MSDN - 结构体 (C#编程指南)


啊,明白了。我一开始也是这样做的,然后发现它没有返回单个实例。这导致我扩展它,希望有不同的结果。很高兴知道没有区别。 - yellow_nimbus

6
因此,虽然 _red 只有一个实例,但 Colors.Red 返回一个全新的实例,这就是存储在 redOne 和 redTwo 中的内容。我的想法正确吗?
是的。由于 Color 是值类型,所以您拥有两个副本。当您使用 ReferenceEquals 时,会将结构体装箱成一个 object,并在两个(完全不同的)装箱对象上调用 Object.ReferenceEquals
另外,如果这是正确的,那么在结构体上使用 static 关键字是否有意义?
主要的重点不在于使其成为单例 - 而是为了简化 API,因此您可以获得颜色的常量值: Colors.RedColors.Green。如果没有这个静态属性,您每次都需要手动创建“红色”颜色。在这种情况下,属性实际上是 Colors 类上一个更好看的工厂方法。

0
变量_red将保存一个单一的Color实例,该实例在初始化时被初始化。然而,由于.NET没有提供任何返回结构体实例引用的方法,因此每次调用属性Red都会返回一个新的结构体实例,其公共和私有字段都是从_red复制的值进行初始化的。此外,由于值类型字段不存储对象,而只是可以根据需要隐式复制到堆对象中的数据(每个值类型都有一个对应的堆对象类型;两者由相同的Type对象描述,但它们不是同一类型),因此每次将值类型转换为堆类型(例如Object)时,都会创建一个新的堆对象实例,并使用从值类型实例初始化的字段值。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接