2010-03-06 12 views
18

Related: How do I create a static local variable in Java?¿C# admite el uso de variables locales estáticas?


Perdón si esto es un duplicado; Estaba bastante seguro de que esto se habría preguntado anteriormente, y miré, pero no encontré una víctima.

¿Es posible para mí crear una variable local estática en C#? ¿Si es así, cómo?

Tengo un método privado estático que se utiliza con poca frecuencia. el método estático usa una Expresión regular, que me gustaría inicializar una vez, y solo cuando sea necesario.

En C, podría hacer esto con una variable estática local. ¿Puedo hacer esto en C#?

Cuando intento compilar este código:

private static string AppendCopyToFileName(string f) 
    { 
     static System.Text.RegularExpressions.Regex re = 
      new System.Text.RegularExpressions.Regex("\\(copy (\\d+)\\)$"); 
    } 

... me da un error:

error CS0106: The modifier 'static' is not valid for this item


Si no hay ninguna variable estática local, supongo que podría aproximar Lo que quiero es crear una pequeña clase estática privada e insertar el método y la variable (campo) en la clase. De esta manera:

public class MyClass 
{ 
    ... 
    private static class Helper 
    { 
     private static readonly System.Text.RegularExpressions.Regex re = 
      new System.Text.RegularExpressions.Regex("\\(copy (\\d+)\\)$"); 

     internal static string AppendCopyToFileName(string f) 
     { 
      // use re here... 
     } 
    } 

    // example of using the helper 
    private static void Foo() 
    { 
     if (File.Exists(name)) 
     { 
      // helper gets JIT'd first time through this code 
      string newName = Helper.AppendCopyToFileName(name); 
     } 
    } 
    ... 
} 

Pensando en esto más, el uso de una clase de ayuda como esto no produciría un mayor ahorro neto en la eficiencia, porque la clase ayudante no se JIT'd o se carga menos que sea necesario. ¿Derecha?

+2

vb.net tiene esta característica –

+1

También puede usar el tipo 'Lazy ' para crear un campo estático con la inicialización lenta, ¿cumple eso con sus requisitos? –

+2

No sabía sobre Lazy. ¿Eso implica que soy perezoso? – Cheeso

Respuesta

10

No, C# no es compatible con esto. Puede acercarse con:

static System.Text.RegularExpressions.Regex re = 
     new System.Text.RegularExpressions.Regex("\\(copy (\\d+)\\)$"); 
private static string AppendCopyToFileName(string f) 
{ 

} 

La única diferencia aquí es la visibilidad de 're'. No creo que C o Java se comporten de manera diferente con respecto a cuándo se inicializa.

+0

Tenía la esperanza de ahorrar el costo de inicialización. Pero al pensarlo bien, sería un ahorro neto más grande incorporar ese método estático en su propia clase, que luego se usa solo "a veces". Los ahorros allí son incluso mayores que los ahorros relacionados con la no inicialización de Regex a menos que sea necesario. Y el problema de seguridad de hilo también está resuelto. – Cheeso

+0

C y C++ definitivamente se comportan muy diferente con respecto al tiempo de inicialización. Garantizan que una función de estática local se inicializa la primera vez que el flujo de control pasa sobre la definición. Es decir. * no * la primera vez que se toca el archivo/clase/función/... que contiene. –

4

Lamentablemente, no. Realmente me encantó esta posibilidad en C.

Tengo una idea de lo que podrías hacer.

Cree una clase que proporcione acceso a valores específicos de instancia, que se conservarán estáticamente.

Algo como esto:

class MyStaticInt 
{ 
    // Static storage 
    private static Dictionary <string, int> staticData = 
     new Dictionary <string, int>(); 

    private string InstanceId 
    { 
     get 
     { 
      StackTrace st = new StackTrace(); 
      StackFrame sf = st.GetFrame (2); 
      MethodBase mb = sf.GetMethod(); 

      return mb.DeclaringType.ToString() + "." + mb.Name; 
     } 
    } 

    public int StaticValue 
    { 
     get { return staticData[InstanceId]; } 

     set { staticData[InstanceId] = value; } 
    } 

    public MyStaticInt (int initializationValue) 
    { 
     if (!staticData.ContainsKey (InstanceId)) 
      staticData.Add (InstanceId, initializationValue); 
    } 
} 

Puede ser utilizado de esta manera ...

class Program 
{ 
    static void Main (string[] args) 
    { 
     // Only one static variable is possible per Namespace.Class.Method scope 
     MyStaticInt localStaticInt = new MyStaticInt (0); 

     // Working with it 
     localStaticInt.StaticValue = 5; 
     int test = localStaticInt.StaticValue; 
    } 
} 

No es una solución perfecta, pero un juguete interesante.

Solo puede tener una variable estática de este tipo por ámbito Namespace.Class.Method. No funcionará en métodos de propiedad; todos se resuelven con el mismo nombre: get_InstanceId.

5

¿Por qué no crear un miembro de solo lectura estático en su clase e inicializarlo en un constructor estático tal vez?

Esto le dará el mismo beneficio de rendimiento, solo se compilará una vez.

+0

+1 por mencionarlo para que sea de solo lectura. – Thomas

+2

sí, sé que los miembros estáticos de solo lectura se inicializan una vez. Pero el objetivo es no inicializar * en absoluto *, a menos que sea necesario. En C, podría hacer eso con una estática local. – Cheeso

+1

@ Cheeso: convierte el miembro estático en 'Lazy '. Para pre-4.0, puede hacer una clase 'internal' en su ensamblado con los mismos miembros que' Lazy '(la implementación básica es bastante simple): http://msdn.microsoft.com/en-us/library/ dd642331 (VS.100) .aspx –

0

Sure. Solo debe declarar la variable estática privada fuera del método.

private static readonly System.Text.RegularExpressions.Regex re = new System.Text.RegularExpressions.Regex("\\(copy (\\d+)\\)$"); 
    private static string AppendCopyToFileName(string f) 
    { 
     //do stuff. 
    } 

Esto es efectivamente lo que está haciendo con la única diferencia de que "re" tiene visibilidad a toda la clase en lugar de sólo el método.

2

¿Qué pasa con esto, ya que sólo desea que sea inicializado si se usa:

private static System.Text.RegularExpressions.Regex myReg = null; 
public static void myMethod() 
{ 
    if (myReg == null) 
     myReg = new Regex("\\(copy (\\d+)\\)$"); 
} 
+0

sí, eso lo haría. Sin embargo, en un escenario de múltiples hilos, creo que podría haber una condición de carrera allí. – Cheeso

+1

¿Qué condición de carrera podría ser? Lo está inicializando con el mismo valor ... – micahtan

1

C# no soporta las variables locales estáticas. Además de lo que se ha publicado anteriormente, aquí hay un enlace a una entrada de blog de MSDN sobre el tema:
http://blogs.msdn.com/b/csharpfaq/archive/2004/05/11/why-doesn-t-c-support-static-method-variables.aspx

+0

FYI: Esta pregunta ya tiene una respuesta aceptada. Desde hace diez meses. – Cheeso

+5

@ Cheeso Sé que esta es una pregunta resuelta. Al mismo tiempo, creo que acumular información relevante es útil para otros que luego encontrarán este hilo. Lo haré en un formato de comentarios a partir de ahora (acabo de recibir mis privilegios de comentario). –

+0

@NickAlexeev es muy útil para ver las respuestas de todos. Los estoy leyendo todo en este momento. Gracias por agregar el tuyo también! –

0

No he visto una buena solución genérica para esto todavía, así que pensé que había llegado con mi propio. Sin embargo, debo señalar que la mayor parte (no siempre) la necesidad de variables locales estáticas es probablemente una señal de que debe refactorizar su código por las razones que han sido establecidas por muchas personas; el estado es algo para el objeto, no un método. Sin embargo, me gusta la idea de limitar el alcance de las variables.

Sin más preámbulos:

public class StaticLocalVariable<T> 
{ 
    private static Dictionary<int, T> s_GlobalStates = new Dictionary<int, T>(); 

    private int m_StateKey; 

    public StaticLocalVariable() 
    { 
     Initialize(default(T)); 
    } 

    public StaticLocalVariable(T value) 
    { 
     Initialize(value); 
    }   

    private void Initialize(T value) 
    { 
     m_StateKey = new StackTrace(false).GetFrame(2).GetNativeOffset(); 

     if (!s_GlobalStates.ContainsKey(m_StateKey)) 
     {     
      s_GlobalStates.Add(m_StateKey, value); 
     } 
    } 

    public T Value 
    { 
     set { s_GlobalStates[m_StateKey] = value; } 
     get { return s_GlobalStates[m_StateKey]; } 
    } 
} 

Esto no es hilo de seguridad, por supuesto, pero no tardaría demasiado trabajo para que así sea. Se puede utilizar de este modo:

static void Main(string[] args) 
{ 
    Console.WriteLine("First Call:"); 
    Test(); 
    Console.WriteLine(""); 
    Console.WriteLine("Second Call:"); 
    Test(); 
    Console.ReadLine(); 
} 

public static void Test() 
{ 
    StaticLocalVariable<int> intTest1 = new StaticLocalVariable<int>(0); 
    StaticLocalVariable<int> intTest2 = new StaticLocalVariable<int>(1); 
    StaticLocalVariable<double> doubleTest1 = new StaticLocalVariable<double>(2.1); 
    StaticLocalVariable<double> doubleTest2 = new StaticLocalVariable<double>(); 

    Console.WriteLine("Values upon entering Method: "); 
    Console.WriteLine(" intTest1 Value: " + intTest1.Value); 
    Console.WriteLine(" intTest2 Value: " + intTest2.Value); 
    Console.WriteLine(" doubleTest1 Value: " + doubleTest1.Value); 
    Console.WriteLine(" doubleTest2 Value: " + doubleTest2.Value); 

    ++intTest1.Value; 
    intTest2.Value *= 3; 
    doubleTest1.Value += 3.14; 
    doubleTest2.Value += 4.5; 

    Console.WriteLine("After messing with values: "); 
    Console.WriteLine(" intTest1 Value: " + intTest1.Value); 
    Console.WriteLine(" intTest1 Value: " + intTest2.Value); 
    Console.WriteLine(" doubleTest1 Value: " + doubleTest1.Value); 
    Console.WriteLine(" doubleTest2 Value: " + doubleTest2.Value);    
} 


// Output: 
// First Call: 
// Values upon entering Method: 
//  intTest1 Value: 0 
//  intTest2 Value: 1 
//  doubleTest1 Value: 2.1 
//  doubleTest2 Value: 0 
// After messing with values: 
//  intTest1 Value: 1 
//  intTest1 Value: 3 
//  doubleTest1 Value: 5.24 
//  doubleTest2 Value: 4.5 

// Second Call: 
// Values upon entering Method: 
//  intTest1 Value: 1 
//  intTest2 Value: 3 
//  doubleTest1 Value: 5.24 
//  doubleTest2 Value: 4.5 
// After messing with values: 
//  intTest1 Value: 2 
//  intTest1 Value: 9 
//  doubleTest1 Value: 8.38 
//  doubleTest2 Value: 9 
+0

No aprobaría usar 'StackTrace' en el código de producción para este objetivo. Es horrible lento en comparación con la definición de una variable estática global. En mi opinión, eso no supera la ventaja de una estática local. Para hacerlo funcionar, una combinación de '[CallerFilePath]' y '[CallerLineNumber]' parece encajar mejor en mi opinión. Estos valores son tiempos de compilación generados: http://stackoverflow.com/questions/22580623/is-callermembername-slow-compared-to-alternatives-when-implementing-inotifypro –

0

Tres años más tarde ...

Usted puede aproximar con una variable local capturado.

class MyNose 
    { 
     private static void Main() 
     { 
      var myNose= new MyNose(); 
      var nosePicker = myNose.CreatePicker(); 

      var x = nosePicker(); 
      var y = nosePicker(); 
      var z = nosePicker(); 
     } 

     public Func<int> CreatePicker() 
     { 
      int boog = 0; 

      return() => boog++; 
     } 
    } 
+0

Ingenioso, pero me temo que esto no funcionará para un objeto - si tiene 'object boog = new object();' se volverá a crear en cada llamada. –

0

la jerarquización de los miembros relacionados en una clase interna como lo han demostrado en cuestión es el más limpio, muy probablemente. No es necesario que inserte su método principal en la clase interna si la variable estática de alguna manera puede obtener la información de la persona que llama.

public class MyClass 
{ 
    ... 
    class Helper 
    { 
     static Regex re = new Regex("\\(copy (\\d+)\\)$"); 
     string caller; 

     internal Helper([CallerMemberName] string caller = null) 
     { 
      this.caller = caller; 
     } 

     internal Regex Re 
     { 
      //can avoid hard coding 
      get 
      { 
       return caller == "AppendCopyToFileName" ? re : null; 
      } 
      set 
      { 
       if (caller == "AppendCopyToFileName") 
        re = value; 
      } 
     } 
    } 


    private static string AppendCopyToFileName(string f) 
    { 
     var re = new Helper().Re; //get 
     new Helper().Re = ...; //set 
    } 


    private static void Foo() 
    { 
     var re = new Helper().Re; //gets null 
     new Helper().Re = ...; //set makes no difference 
    } 
} 
  1. Usted puede evitar la codificación dura de nombres de métodos en el establecimiento a través de algunos trucos de árboles de expresión.

  2. Puede evitar el constructor de ayuda y hacer la propiedad estática, pero necesita obtener la información de la persona que llama dentro de la propiedad, mediante el uso de StackTrace.

Por último, siempre hay const posible dentro de un método, pero entonces uno, no es la variable, dos, solamente se permite compilar constantes de tiempo. Solo diciendo.

1

A lo largo de las líneas de Henk de y la respuesta de BarretJ, creo que se puede evitar el costo de inicialización y acercarse aún más mediante el uso de una propiedad,

private Regex myReg = null; 
private Regex MyReg 
{ 
    get { 
     if (myReg == null) 
      myReg = new Regex("\\(copy (\\d+)\\)$"); 
     return myReg; 
    } 
} 

Entonces sólo tiene que utilizar MyReg (nótese la mayúscula 'M' en MyReg) en todas partes en tu código. Lo bueno de esta solución es que (aunque el getter es una función bajo el capó) la semántica de las propiedades significa que puedes escribir código como si MyReg fuera una variable.

Lo anterior es la forma en que configuro las "constantes de tiempo de ejecución" que requieren una inicialización única en el tiempo de ejecución.

Hago lo mismo usando tipos que aceptan nulos, también. Por ejemplo,

private bool? _BoolVar = null; 
private bool BoolVar 
{ 
    get { 
     if (_BoolVar.HasValue) 
      return (bool)_BoolVar; 
     _BoolVar = /* your initialization code goes here */; 
     return (bool)_BoolVar; 
    } 
} 

Entonces simplemente use BoolVar como un bool normal normal en su código. No utilizo _BoolVar interno (la tienda de respaldo para la propiedad BoolVar) porque simplemente no es necesario, recuerde que esto es como una constante de tiempo de ejecución, por lo que no existe un setter. Sin embargo, si tuviera que cambiar el valor de la constante de tiempo de ejecución por alguna razón, lo haría directamente en la variable anulable _BoolVar.

La inicialización podría ser bastante complicado. Pero solo se ejecuta una vez y solo en el primer acceso de la propiedad. Y tiene la opción de forzar la reinicialización del valor de la constante de tiempo de ejecución ajustando _BoolVar en nulo.

0

he desarrollado una clase estática que se ocupa de este problema de una manera bastante sencilla:

using System.Collections.Generic; 
using System.Runtime.CompilerServices; 

public static class StaticLocal<T> 
{ 
    static StaticLocal() 
    { 
     dictionary = new Dictionary<int, Dictionary<string, Access>>(); 
    } 

    public class Access 
    { 
     public T Value { get; set; } 

     public Access(T value) 
     { 
      Value = value; 
     } 

    } 

    public static Access Init(T value, [CallerFilePath]string callingFile = "", 
             [CallerMemberName]string callingMethod = "", 
             [CallerLineNumber]int lineNumber = -1) 
    { 
     var secondKey = callingFile + '.' + callingMethod; 
     if (!dictionary.ContainsKey(lineNumber)) 
      dictionary.Add(lineNumber, new Dictionary<string, Access>()); 
     if (!dictionary[lineNumber].ContainsKey(secondKey)) 
      dictionary[lineNumber].Add(secondKey, new Access(value)); 
     return dictionary[lineNumber][secondKey]; 
    } 

    private static Dictionary<int, Dictionary<string, Access>> dictionary; 

} 

Puede implementarse dentro de un método como este:

var myVar = StaticLocal<int>.Init(1); 
Console.Writeline(++myVar.Value); 

En cada llamada posterior a la método, el valor contenido en myVar.Value será el último en el que se configuró para que las llamadas repetidas causen una secuencia de números naturales. La función Init() solo establece el valor si no se ha inicializado previamente. De lo contrario, solo devuelve una referencia a un objeto que contiene el valor.

Utiliza los atributos [CallerFilePath], [CallerMemberName] y [CallerLineNumber] para rastrear a qué elemento del diccionario se hace referencia. Esto elimina la posibilidad de colisiones entre métodos con los mismos nombres o llamadas de los mismos números de línea.

algunas advertencias sobre su uso:

  • Como otros han dicho, vale la pena considerar si lo que está haciendo realmente requiere el uso de variables locales estáticas. Su uso a veces puede ser una señal de que su diseño es defectuoso y podría usar alguna refactorización.
  • Este método para tratar el problema implica un par de capas de indirección, ralentizando la ejecución de su programa. Solo debe usarse si justifica ese costo.
  • Las variables locales estáticas pueden ayudarlo a manejar el hecho de tener demasiados miembros declarados en su clase, compartimentalizándolos en el lugar en que se usan. Esto debe sopesarse con el costo del tiempo de ejecución, pero a veces puede valer la pena. Por otro lado, tener tantos miembros declarados dentro de una clase puede ser una indicación de problemas de diseño que vale la pena considerar.
  • Debido a que estos valores continúan en la memoria después de completar la ejecución de los métodos, debe tener en cuenta que usarlos para almacenar grandes cantidades de memoria evitará la recolección de basura hasta que el programa finalice, disminuyendo así los recursos disponibles.

Este enfoque es probablemente excesivo para la mayoría de los casos en los que desearía usar variables locales estáticas. Su uso de indirección para tratar con archivos, métodos y líneas separados puede ser innecesario para su proyecto, en cuyo caso puede simplificarlo para satisfacer sus necesidades.

+0

Haría la primera clave en el diccionario '[CallerFilePath]' y la segunda tecla '[CallerLineNumber]'. No hay necesidad de '[CallerMemberName]'. Y otra advertencia es que la inicialización de dos variables estáticas locales como esta en el mismo archivo en la misma línea produce un comportamiento inesperado. +1 Obtuve este código, con los ajustes mencionados anteriormente. –

2

No en C#, solo en Visual Basic.NET:

Sub DoSomething() 
    Static obj As Object 
    If obj Is Nothing Then obj = New Object 
    Console.WriteLine(obj.ToString()) 
End Sub 

VB.NET tiene muchas cosas buenas que C# no tiene, por eso elijo VB.NET.

+0

En serio. VB.Net es compatible con locales estáticos? Y C#, que se ve como C, pero no lo es, donde C tiene locales estáticos, ¿no lo tiene? ¿¿¿¡Por qué!??? Gracias por mencionarlo. ¡Voy a cambiar a VB.Net ahora mismo! –

Cuestiones relacionadas