【问题标题】:Is implementing a singleton using an auto-property a good idea?使用自动属性实现单例是个好主意吗?
【发布时间】:2010-01-22 12:46:31
【问题描述】:

我最近发现了自动属性并且非常喜欢它们。此刻,我正试图在我可以使用的任何地方使用它们。并不是真的能够在任何地方使用它们,而是更多地了解它们在大多数情况下的工作情况。

现在我正在制作一个单例,然后想:“嘿,让我们在这里也试试自动属性吧”。

public class MySingleton
{
    public static MySingleton MySingleton { get; private set; }

    private MySingleton() {}

    static MySingleton() { MySingleton = new MySingleton(); }
}

所以我的问题是:“实现这样的单例是个好主意吗?”

我不是在问单身人士是否是一个好主意。

【问题讨论】:

    标签: c# singleton automatic-properties


    【解决方案1】:

    我个人不会那样做。我不喜欢将自动实现的属性与您永远不会调用的私有 setter 一起使用 真正 您想要一个由只读变量支持的只读属性。只需多一行代码就可以更明确地表达您的意思:

    public sealed class MySingleton
    {
        private static readonly MySingleton mySingleton;
        public static MySingleton MySingleton { get { return mySingleton; } }
    
        private MySingleton() {}
    
        static MySingleton() { mySingleton = new MySingleton(); }
    }
    

    这种方式甚至没有人试图更改单例类以重新分配属性或变量,因为编译器会阻止它们。他们必须添加一个 setter 和/或将变量设为非只读,这是一个更大的变化 - 希望他们会重新考虑。

    换句话说:

    • 是的,它会起作用的。
    • 不,我认为这不是一个好主意。

    从 C# 6 开始,使用只读自动实现的属性会更容易:

    public sealed class MySingleton
    {
        public static MySingleton MySingleton { get; } = new MySingleton();    
        private MySingleton() {}         
        static MySingleton() {}
    }
    

    【讨论】:

    • 我强烈反对。发明自动属性是为了使代码更简洁,从而使代码更具可读性。认为别人更难搞砸你的设计的论点不是很令人信服。
    • @Philippe:发明了自动属性以使代码更简洁替换代码与原始代码等效。这里没有这样的等价物:您正在用读写属性替换只读属性,即使它有一个私有设置器。这可能看起来微不足道,但我更喜欢我的代码来表达我的意图 - 我的意图是该值只能设置一次。自动实现的属性表达了不同的意图。
    • @Philippe:所以只要 API 对 consumer 来说是可以的,那么实现是什么样的并不重要?维护者呢?我喜欢我的代码向下一个将要查看源代码的人记录意图,而不仅仅是消费者。
    • 您好 - C# 中的新语法糖可以像这样设置 auto 属性,不是吗? public static SomeSingleton Instance { get; } = new SomeSingleton();
    • @Thomas:不,它不应该这样做——它是一个由只读字段支持的简单属性。
    【解决方案2】:

    我会从与 Jon 稍有不同的方向来处理这个问题。 无论对象是否为单例,首先在逻辑上是否最好将其建模为属性?

    属性应该代表……属性。 (Captain Obvious 再次出击!)你知道的。颜色。长度。高度。姓名。家长。所有你在逻辑上认为是事物属性的东西。

    我无法想象逻辑上是单例的对象的属性。也许你想出了一个我没有想到的场景;今天早上我还没有任何饮食 Dr. Pepper。但我怀疑这是对模型语义的滥用。

    您能描述一下单例是什么,以及为什么您认为它是某物的属性吗?

    话虽如此,我自己也经常使用这种模式;通常是这样的:

    class ImmutableStack<T>
    {
        private static readonly ImmutableStack<T> emptystack = whatever;
        public static ImmutableStack<T> Empty { get { return emptystack; } }
        ...
    

    “空”在逻辑上是不可变堆栈的属性吗?不。这就是能够说的令人信服的好处

    var stack = ImmutableStack<int>.Empty;
    

    胜过我对属性在逻辑上是属性的渴望。说

    var stack = ImmutableStack<int>.GetEmpty();
    

    只是看起来很奇怪。

    在这种情况下,最好是拥有一个只读字段和一个常规属性,还是一个静态 ctor 和一个自动属性,这似乎不是一个有趣的问题,而不是首先使其成为一个属性。在“纯粹主义者”的心情下,我可能会站在 Jon 一边,并将其设为只读字段。但我也经常使用为逻辑上不可变的对象制作带有私有设置器的 autoprops 的模式,只是出于懒惰。

    如何从各个方面考虑问题?

    【讨论】:

    • 嗯,很有趣。我从来没有真正以这种方式考虑过属性。对我来说,它们一直只是访问器方法的替代品。在某种程度上,我将访问器方法视为改进公共字段的一种方式(部分隐藏,放入接口,添加一些检查信息)。然后properties能够以比getter和setter更直观的方式获得。所以对我来说,一个属性在逻辑上永远不必是类所代表的事物的属性。对我来说,属性就是字段从外部看的样子。
    • 您能否在ImmutableStack&lt;int&gt;.Empty 示例中详细说明类似属性的语法的好处?我想更好地了解您在这里获得了什么好处,特别是因为在 LINQ 中 Enumerable.Empty&lt;T&gt;() 类似于您在该示例中避免使用的语法类型。
    • @LBushkin -- 我想“Empty”、“Empty()”和“GetEmpty()”都是很小的差异。我认为“令人信服的利益”是夸大其词! :-)
    【解决方案3】:

    我看不出有什么不正确的理由。毕竟,自动属性只是私有(编译器生成)支持字段的访问器的语法糖。

    【讨论】:

      【解决方案4】:

      当然,我认为这没有任何问题。

      【讨论】:

        【解决方案5】:

        汽车属性?不,我不会,但在单例上使用二传手,是的,我做到了。我认为你想要对它进行更多的控制,而不是自动属性给你的。

        ...这里非常欢迎有建设性的反馈。在这家受人尊敬的公司中,这感觉像是一个勇敢(或愚蠢)的举动......

        我的场景,一个 WPF 应用程序,它有一个可以加载和保存的当前项目。当前项目设置在整个应用程序中使用...

        • 在 UI 中的 WPF 绑定中,因此用户可以更改设置,因此INotifyPropertyChanged 界面。
        • 我也使用Fody.PropertyChanged,但它不会更改静态属性,因此NotifyStaticPropertyChanged
        • INotifyPropertyChanged 可以很好地与单例属性上的 WPF 绑定配合使用。
        • Settings 的一个实例是使用 JSON.NET [de] 序列化的,因此单例上的 [JsonIgnore] 属性。我在将其加载到单例或将其保存到磁盘之前对其进行验证。
        • 记录器是Serilog。你记录东西,对吧?
        • 单例是带有public getter 的public,因为WPF 绑定仅适用于公共属性。 internal 设置器不会影响它。 Settings 的所有属性对于 WPF 绑定都是公开的。

        我在代码中留下了所有“噪音”,因为有些人可能会觉得它很有用。

        class Settings : INotifyPropertyChanged
        {
            private static Settings _currentSettings = new Settings();
        
            /// <summary> The one and only Current Settings that is the current Project. </summary>
            [JsonIgnore] // so it isn't serialized by JSON.NET
            public static Settings CurrentSettings //  http://csharpindepth.com/Articles/General/Singleton.aspx 
            {
                get { return _currentSettings; }
        
                // setter is to load new settings into the current settings (using JSON.NET). Hey, it works. Probably not thread-safe.
                internal set 
                {
                    Log.Verbose("CurrentSettings was reset. Project Name: {projectName}", value.ProjectName);
                    _currentSettings = value;
                    _currentSettings.IsChanged = false;
                    NotifyStaticPropertyChanged("CurrentSettings");
                }
            }
        
            // http://10rem.net/blog/2011/11/29/wpf-45-binding-and-change-notification-for-static-properties
            /// <summary> Fires when the Curent CurrentTvCadSettings is loaded with new settings
            ///           Event queue for all listeners interested in StaticPropertyChanged events. </summary>
            public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged = delegate { };
            private static void NotifyStaticPropertyChanged(string propertyName)
            {
                StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
            }
        
            // various instance properties including ....
        
            public string ProjectName {get; set;}
        
            [JsonIgnore]
            public bool IsChanged { get; set; } 
        }
        

        用于将单例设置为新加载的项目,settings 很简单

        Settings settings = new Settings();
        // load, save, deserialize, set properties, go nuts
        Settings.CurrentSettings = settings;
        

        setter 可能不是线程安全的,但我只将它设置在 UI 线程的一个地方,所以我并不害怕。您可以按照http://csharpindepth.com/Articles/General/Singleton.aspx 的受人尊敬的建议使其线程安全

        我意识到 OP 没有询问 WPF,但我认为这与说明您可能想要设置单例的原因有关。我这样做是因为它是最简单的解决方案。

        【讨论】:

          【解决方案6】:

          总结 + 使用 lambda 样式 (>= C# 6) 又名计算属性(又名表达式主体成员)的替代语法:

          代码在功能上完全等同于 Jon Skeet 的答案,这里再次使用“Instance”。我不希望为此获得荣誉,但我认为这个更新的总结并在一个地方解释是值得的,因为这个 C#6 问题和答案扩展了讨论 Singleton 变体的旧封闭线程。

          您可能会争辩说,带有显式缺失集的自动属性样式更清楚地表达了只读属性的意图,但最终它是基于样式的,并且这两种样式在现代 C# 中都很常见。

          public sealed class MySingleton
          {
              public static MySingleton Instance => new MySingleton(); // Assure that instantiation is only done once (because of static property) and in threadsafe way, and as this is an alternative style for a readonly-property, there is no setter
              private MySingleton() {} // Assure, that instantiation cannot be done from outside the class        
              static MySingleton() {} // Assure partly lazyness, see below     
          }
          

          所有细节和历史参考都集中在一处:

          关于懒惰的讨论:http://csharpindepth.com/Articles/General/Beforefieldinit.aspx
          简化总结:只要在类中没有引入其他静态字段/属性,上述实现就会表现得很懒惰。 (但这是一个可能依赖于 .NET 实现的领域。)

          关于 Singleton 和线程安全的不同实现(旧 C# 样式)的讨论: http://csharpindepth.com/Articles/General/Singleton.aspx

          原帖(已关闭): Singleton Pattern for C#

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-03-09
            相关资源
            最近更新 更多