【问题标题】:Accessing a singleton class in c# with this;用这个访问 C# 中的单例类;
【发布时间】:2019-07-23 08:37:13
【问题描述】:

在寻找获取单例类实例的方法时,我发现了许多不同的方法(有些简单,有些复杂),但在搞砸时,我找到了一种在其他任何地方都找不到的方法。

所以我基本上做的是:

public class Foo
    { 
       public static Foo Invoker;
       public Foo() 
       {
          Invoker = this;

       }
       public void Method1()
       { 
         //.....
       }
    }

然后从另一个班级

public class Foo2
    { 

       public Foo2() 
       {
          //.....

       }
       public void Main()
       {
          var foo = Foo.Invoker;
          //or
          Foo.Invoker.Method1();
       }
    }

我的应用程序是单线程的,所以我不关心线程安全(我应该吗?),那么这种方法可能会导致我遗漏任何其他问题吗?

【问题讨论】:

  • 是什么阻止人们自己实例化Foo? (例如new Foo())编辑:实际上。这甚至都行不通,因为构造函数从未被调用过。
  • Foo 不是单身
  • John Skeet 有一篇关于singleton的非常好的文章
  • 不允许创建一个新实例,但是这样做没有意义,因为我想访问已经存在的值(也许这在我的示例中并不明显)

标签: c# design-patterns singleton instance


【解决方案1】:

首先,您的“单例”模式很容易被打破。假设我将在您的应用程序中创建两个 Foo 实例(为了清楚起见,将类的名称 Foo1 更改为 Bar):

var firstFoo = new Foo();

var bar = new Bar();

// Will access firstFoo when it calls Foo.Invoker
bar.Main();

var secondFoo = new Foo();

// Will access secondFoo when it calls Foo.Invoker. Huh?
bar.Main();

另一个问题:假设我使用Bar,而没有初始化任何Foo 实例:

var bar = new Bar();

// Will throw a NullReferenceException, because Foo.Invoker is not yet initialized.
bar.Main();

作为一个粗略的经验法则,您不应该从实例中设置静态字段,因为它会导致这种情况。 其次,Bar 可能首先不需要知道Foo 是单例。您可以简单地将Foo 注入Bar 的构造函数中。

public class Bar
{ 
    private Foo foo;
    public Bar(Foo foo) => this.foo = foo ?? throw new ArgumentNullException(nameof(foo));

    public void Main()
    {
        // Now we know it is not null and, for Bar, it does not matter whether it's a singleton or not.
        foo.Method1();
    }
}

这样您可以在应用程序中更轻松地管理 Foo 实例:

var firstFoo = new Foo();
var bar = new Bar(firstFoo);

// Does not make a difference now.
var secondFoo = new Foo();

这样,您还可以利用 NInject 或 Microsoft.Extensions.DependencyInjection 等依赖注入容器来为您管理单例。

如果您真的想创建单线程单例模式,我会阅读 Jon Skeet 的 blog post 关于单例的文章(好读!)。

创建单例的最简单方法就是这种方法。通过这种方式,您可以在静态属性上创建一个 Foo 实例,该实例永远无法更改。但请阅读博文了解更高级的模式:

public class Foo
{
    public static Foo Invoker { get; } = new Foo();

    // Private constructor makes sure the only instance of Foo is created here.
    private Foo()
    {
    }
}

编辑

如果您想确保应用程序中对 Foo 的所有引用都指向最后创建的 Foo 实例,您可以尝试以下操作:

interface IFoo
{
    void Method1();
}

class Foo : IFoo
{
    private static int index = 1;
    private int id;
    private static NestedFoo invoker = new NestedFoo();
    public static IFoo Invoker
    {
        get
        {
            if (invoker.Instance == null)
            {
                Create();
            }

            return invoker;
        }
    }
    private Foo(int id) => this.id = id;
    public static IFoo Create()
    {
        var foo = new Foo(index++);
        invoker.Instance = foo;
        return invoker;
    }

    public void Method1()
    {
        Console.WriteLine(this.id);
    }

    private class NestedFoo : IFoo
    {
        public Foo Instance { get; set; }
        public void Method1() => Instance.Method1();
    }
}

现在您将始终拥有对同一个 foo 实例的引用:

var foo = Foo.Create();
foo.Method1(); // 1
var foo2 = Foo.Create();
foo.Method1(); // 2
foo2.Method1(); // 2

【讨论】:

  • 好吧,我认为我的方法并非万无一失,但我不明白最后一段代码与我所做的有什么不同?是不是因为我的Invoker = this;在构造函数中?
  • @KyriakosXatzisavvas 是的,这将导致您的单例属性为每个创建的 Foo 更改。注意我现在在帖子中添加了一个私有构造函数。这样,您可以轻松管理 Foo 的单个实例。
  • 如果我只需要最后创建的 Foo 实例怎么办?我要说的是这对我有用,因为我只需要 Foo 的一个活动实例,当我通过 Foo.Invoker 引用它时,我知道我总是得到最后一个实例(我不完全关心以前实例的值)
  • @KyriakosXatzisavvas 我想不出一个有意义的情况?这让事情变得非常复杂。您不应该将Foo 传递给BarMain() 方法吗?必须有办法绕过你试图创造的这种模式。或许您应该分享您正在尝试解决的问题,以便我们就如何改进您的设计提出建议。
  • @KyriakosXatzisavvas 查看我的编辑以获取始终保持对同一个 Foo 实例的引用的方法
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多