Pergunta

Eu estive sentado sobre esta ideia por muito tempo e gostaria de ouvir o que vocês pensam sobre isso.

O idioma padrão para escrever um singleton é mais ou menos o seguinte:

public class A {
...
   private static A _instance;

   public static A Instance() {
      if(_instance == null) {
          _instance = new A();
      }

      return _instance;
   }
...
}

Aqui eu estou propondo uma outra solução:

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;
   }
...
}

A idéia básica por trás disso é que o custo de execução de 1 dereference e exceção não lançada é menor do que a de um cheque nulo. Eu tentei para medir o ganho de desempenho potentional e aqui estão meus números:

Sleep 1seg (try / catch) : 188788ms

Sleep 1seg (nullcheck) : 207485ms

E o código de teste:

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
}

O ganho de desempenho resultante é quase 10%, a questão é se é um micro-op, ou pode oferecer ganho significativo de performance para aplicações Singleton felizes (ou de middleware, como, logging)?

Foi útil?

Solução

O que você está perguntando sobre é a melhor forma de implementar um padrão Singleton ruim. Você deve ter um olhar para o artigo de Jon Skeet em como implementar o padrão singleton em C # . Você verá que há muito melhores maneiras (mais seguros) e eles não sofrem os mesmos problemas de desempenho.

Outras dicas

Esta é uma horrível maneira de fazer isso. Como outros apontaram, exceções só deve ser usado para o tratamento de situações excepcionais e inesperadas. E porque nós fazer essa suposição, muitas organizações executar seu código em contextos que buscam agressivamente para fora e uns relatório exceções, , mesmo tratados , porque a exceção de referência nula tratado é quase certamente um erro . Se seu programa é inesperadamente dereferencing memória inválido e continuando alegremente junto, em seguida, as chances são boas de que algo está profundamente, mal quebrado em seu programa e deve ser trazido à atenção de alguém.

Do not "lobo grito" e deliberadamente construir uma situação que parece terrivelmente quebrado, mas é na verdade por design. Isso é apenas fazer mais trabalho para todos. Existe um padrão, simples, forma aceita para fazer um singleton em C #; fazer isso, se é isso que quer dizer. Não tente inventar alguma coisa louca que viola os princípios da boa programação. As pessoas mais inteligentes do que eu ter projetado uma implementação singleton que as obras; é tolo não usá-lo.

Eu aprendi isso da maneira mais difícil. Para encurtar a história, uma vez eu deliberadamente utilizado um teste que faria a maior parte do tempo de memória dereference ruim em um cenário principal em tempo de execução VBScript. Fiz questão de lidar com cuidado a exceção e recuperar corretamente, ea equipe ASP fui louco naquela tarde. De repente, todos os seus cheques para a integridade do servidor começou relatando que um grande número de suas páginas estavam a violar a integridade da memória e recuperação a partir daí. Acabei rearquitetar a implementação desse cenário por fazer apenas caminhos de código que não resultaram em exceções.

A exceção nula ref deve ser sempre um erro, período. Quando você lidar com uma exceção nula ref, você está escondendo um bug.

Mais meditando sobre as classificações de exceção:

http://ericlippert.com/2008/09/10/vexing- exceções /

Eu acho que se a minha candidatura é chamar o construtor de um singleton muitas vezes suficiente para um aumento de desempenho de 10% a qualquer coisa significa que eu seria um pouco preocupado.

Dito isto, nem versão é thread-safe. Concentre-se em fazer as coisas como que na primeira à direita.

"Devemos esquecer pequena eficiências, digamos cerca de 97% do tempo: otimização prematura é a raiz de todo mal ".

Esta otimização parece trivial. Eu sempre ser ensinado aos blocos uso try / catch apenas para condições de captura que seriam difíceis ou impossíveis de verificar com uma instrução if.

Não é que a sua abordagem proposta não iria funcionar. Ele só não é significativamente melhor do que a forma original.

O "melhorada" aplicação tem um grande problema e que é que ele dispara uma interrupção. Uma regra de ouro é que a lógica da aplicação não deve ser dependente de exceções disparando.

Você nunca deve usar o tratamento de exceção para o fluxo de controle.

Além disso, em vez de:

 if(_instance == null) {
          _instance = new A();
      }

      return _instance;

você pode fazer:

return _instance ?? (_instance = new A());

O que é semanticamente o mesmo.

porque não basta:

public class A {
...
private static A _instance = new A();

public static A Instance() {
  return _instance;
}
...
}

Eu acho que a sua solução é perfeitamente aceitável embora eu acho que existem algumas coisas que você deve (e raramente são) considerar antes de implementar um singleton carregado Panti-padrão preguiçoso.

  1. Você poderia fornecer uma versão DI em vez disso, seria uma bastam recipiente unidade com um contrato de interface? Isso permitiria que você troque a implementação mais tarde se necessário, também faz testes muito mais fácil.
  2. Se você deve insistir em usar um singleton, você realmente precisa de uma implementação lazy-carregado? O custo de criar a instância no construtor estático implícita ou explicitamente só será executada quando a classe tem sido referenciado em tempo de execução, e na minha experiência quase todos os singletons são sempre apenas referenciada quando querem ter acesso ao "Instância" de qualquer maneira.

Se você precisa implementá-lo da maneira que você descreveu, você faria isso como o seguinte.

Atualizar : Eu atualizei o exemplo de tipo de classe em vez de bloqueio em vez de uma variável de bloqueio como pontos de Brian Gideon fora a instância poderia estar em uma metade inicializado estado. Qualquer uso de lock(typeof()) é fortemente desaconselhadas e eu recomendo que você nunca usar essa abordagem.

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; }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top