我一直坚持这个想法很长一段时间,并希望听到你们的想法。
写单身的标准习语大致如下:
public class A {
...
private static A _instance;
public static A Instance() {
if(_instance == null) {
_instance = new A();
}
return _instance;
}
...
}
我在这里提出另一个解决方案:
public class A {
...
private static A _instance;
public static A Instance() {
try {
return _instance.Self();
} catch(NullReferenceExceptio) {
_instance = new A();
}
return _instance.Self();
}
public A Self() {
return this;
}
...
}
它背后的基本思想是1个解除引用和不再引用异常的运行时成本小于一个空检查的运行时成本。我试图测量潜在的性能提升,这是我的数字:
睡眠1秒(尝试/捕获):188788ms
睡眠1秒(nullcheck):207485ms
测试代码:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Diagnostics;
public class A
{
private static A _instance;
public static A Instance() {
try {
return _instance.Self();
} catch(NullReferenceException) {
_instance = new A();
}
return _instance.Self();
}
public A Self() {
return this;
}
public void DoSomething()
{
Thread.Sleep(1);
}
}
public class B
{
private static B _instance;
public static B Instance() {
if(_instance == null) {
_instance = new B();
}
return _instance;
}
public void DoSomething()
{
Thread.Sleep(1);
}
}
public class MyClass
{
public static void Main()
{
Stopwatch sw = new Stopwatch();
sw.Reset();
sw.Start();
for(int i = 0; i < 100000; ++i) {
A.Instance().DoSomething();
}
Console.WriteLine(sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
for(int i = 0; i < 100000; ++i) {
B.Instance().DoSomething();
}
Console.WriteLine(sw.ElapsedMilliseconds);
RL();
}
#region Helper methods
private static void WL(object text, params object[] args)
{
Console.WriteLine(text.ToString(), args);
}
private static void RL()
{
Console.ReadLine();
}
private static void Break()
{
System.Diagnostics.Debugger.Break();
}
#endregion
}
最终的性能提升几乎是10%,问题在于它是微操作,还是可以为单例开心应用程序(或者它的中间件,如日志记录)提供显着的性能提升?
答案 0 :(得分:17)
您所询问的是实施不良单例模式的最佳方法。你应该看看Jon Skeet关于how to implement the singleton pattern in C#的文章。您会发现有更好(更安全)的方式,并且它们不会遇到相同的性能问题。
答案 1 :(得分:7)
这是一种可怕的方式。正如其他人所指出的那样,异常只应用于处理异常的意外情况。而且因为我们做出了这个假设,许多组织在上下文中运行他们的代码,积极地寻找和报告异常,甚至处理,因为处理的空引用异常几乎肯定是一个bug 。如果你的程序出乎意料地取消引用无效内存并继续快乐地继续,那么你的程序中的某些东西会被深深地打破并且应该引起别人注意。
不要“哭狼”并故意构建一个看起来非常破碎但实际上是设计的情况。这只是为每个人做更多的工作。有一种标准的,直接的,可接受的方式来在C#中制作单例;如果你的意思是这样的话。不要试图发明一些违反良好编程原则的疯狂事物。比我聪明的人设计了一个有效的单例实现;不使用它是愚蠢的。
我很难学到这一点。长话短说,我曾故意使用一种测试,大多数时候会在VBScript运行时的主线方案中取消引用坏内存。我确保小心处理异常并正确恢复,那天下午ASP团队发疯了。突然间,他们对服务器完整性的所有检查都开始报告他们的大量页面违反了内存完整性并从中恢复。我最终将该场景的实现重新架构为仅执行不会导致异常的代码路径。
null ref异常应该始终是一个bug,句点。 当你处理空引用异常时,你隐藏了一个bug。
更多关于异常分类的思考:
答案 2 :(得分:6)
我认为如果我的应用程序经常调用单例的构造函数来提高10%的性能,那就意味着我会更加担心。
那就是说,这两个版本都不是线程安全的。专注于先把这样的事情做好。
“我们应该忘记小事 效率,约占97% 时间:过早优化是 万恶之源。“
答案 3 :(得分:2)
这种优化似乎微不足道。我总是被教导只使用try / catch块来捕获难以或不可能用if语句检查的条件。
并非你所提议的方法不起作用。它只是没有明显优于原始方式。
答案 4 :(得分:0)
我认为你的解决方案是完全可以接受的,尽管我认为在实现延迟加载的单例panti模式之前你应该(并且很少)考虑一些事情。
如果你需要按照你所描述的方式实现它,你可以像下面那样实现它。
UPDATE :我已经更新了示例,而不是锁定类型而不是锁定变量,因为Brian Gideon指出实例可能处于半初始化状态。使用lock(typeof())
强烈建议不要使用,我建议您不要使用此方法。
public class A {
private static A _instance;
private A() { }
public static A Instance {
get {
try {
return _instance.Self();
} catch (NullReferenceException) {
lock (typeof(A)) {
if (_instance == null)
_instance = new A();
}
}
return _instance.Self();
}
}
private A Self() { return this; }
}
答案 5 :(得分:0)
“改进”的实现有一个很大的问题,就是它会触发一个中断。根据经验,应用程序逻辑不应依赖于异常触发。
答案 6 :(得分:0)
永远不要对控制流使用异常处理。
此外,而不是:
if(_instance == null) {
_instance = new A();
}
return _instance;
你可以这样做:
return _instance ?? (_instance = new A());
在语义上是相同的。
答案 7 :(得分:0)
为什么不呢:
public class A {
...
private static A _instance = new A();
public static A Instance() {
return _instance;
}
...
}