Подтвердить что ты не робот

Поддерживает ли С# использование статических локальных переменных?

Связанный: Как создать статическую локальную переменную в Java?


Простите, если это дубликат; Я был уверен, что это было бы задано ранее, и я выглядел, но не нашел обмана.

Возможно ли создать статическую локальную переменную в С#? Если да, то как?

У меня есть статический частный метод, который используется редко. статический метод использует регулярное выражение, которое я хотел бы инициализировать один раз и только тогда, когда это необходимо.

В C я мог бы сделать это с помощью локальной статической переменной. Могу ли я сделать это на С#?

Когда я пытаюсь скомпилировать этот код:

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

... это дает мне ошибку:

error CS0106: Модификатор 'static' недействителен для этого элемента


Если нет локальной статической переменной, я полагаю, что я мог бы приблизиться к тому, что мне нужно, создав крошечный новый закрытый статический класс и вставив в класс как метод, так и переменную (поле). Вот так:

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

Размышляя об этом больше, используя класс-помощник, подобный этому, вы получите большую чистую экономию в эффективности, потому что класс Helper не будет JIT'd или загружен, если это необходимо. Правильно?

4b9b3361

Ответ 1

Нет, С# не поддерживает это. Вы можете подойти с:

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

private static string AppendCopyToFileName(string f)
{

}

Единственная разница здесь - это видимость 're'. Это выставлено классу не только методу.

Переменная re будет инициализирована при первом использовании содержащего класса. Так что держите это в специализированном маленьком классе.

Ответ 2

К сожалению, нет. Я действительно любил эту возможность в C.

У меня есть идея, что вы можете сделать.

Создайте класс, который обеспечит доступ к конкретным значениям экземпляра, которые будут сохраняться статически.

Что-то вроде этого:

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

Может использоваться таким образом...

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

Это не идеальное решение, а интересная игрушка.

У вас может быть только одна статическая переменная этого типа в пространстве имен. Class.Method. Не будет работать в методах свойств - все они решаются с тем же именем - get_InstanceId.

Ответ 3

Почему бы не создать static readonly член только для static readonly в вашем классе и инициализировать его в статическом конструкторе?

Это даст вам тот же выигрыш в производительности - он будет инициализирован только один раз.

Ответ 4

Не в С#, только в 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 много приятных вещей, которые нет у С#, поэтому я выбираю VB.NET.

Ответ 5

Как насчет этого, поскольку вы хотите, чтобы он был инициализирован, если он используется:

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

Ответ 7

В соответствии с ответами Henk и BarretJ, я думаю, вы можете избежать стоимости инициализации и подойти еще ближе, используя свойство,

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

Затем просто используйте MyReg (обратите внимание на верхний регистр "M" в MyReg) везде в вашем коде. Самое приятное в этом решении заключается в том, что (хотя getter - это вызов функции под капотом), семантика свойств означает, что вы можете написать код, как если бы MyReg был переменной.

Выше описано, как я настраиваю "константы времени выполнения", которые требуют одноразовой инициализации во время выполнения.

Я делаю то же самое, используя типы с нулевым значением. Например,

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

Затем просто используйте BoolVar, как обычный нормальный bool в вашем коде. Я не использую внутренний _BoolVar (хранилище резервных копий для свойства BoolVar), потому что мне просто не нужно, помните, что это похоже на константу времени выполнения, поэтому нет настройки. Однако, если мне по какой-то причине нужно было изменить значение константы времени выполнения, я бы сделал это непосредственно по переменной с нулевым значением _BoolVar.

Инициализация может быть довольно сложной. Но он выполняется только один раз и только при первом доступе к собственности. И у вас есть выбор для принудительной повторной инициализации значения постоянной времени выполнения, установив для _BoolVar значение null.

Ответ 8

Конечно. Вам просто нужно объявить приватную статическую переменную вне метода.

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

Это эффективно то, что вы делаете, с той лишь разницей, что "re" имеет видимость для всего класса, а не только для этого метода.

Ответ 9

Я не видел хорошего общего решения этого, но я думал, что придумаю свое. Однако я должен отметить, что по большей части (не всегда) необходимость статических локальных переменных, вероятно, является признаком того, что вы должны реорганизовать свой код по причинам, которые были высказаны многими людьми; состояние - это что-то для объекта, а не для метода. Однако мне нравится идея ограничить область переменных.

Без дальнейших церемоний:

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

Конечно, это не потокобезопасность, но для этого не потребуется слишком много работы. Его можно использовать так:

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

Ответ 10

Спустя три года...

Вы можете аппроксимировать его с помощью захваченной локальной переменной.

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

Ответ 11

Вложение связанных членов во внутренний класс, как вы показали, является самым чистым, наиболее вероятно. Вам не нужно толкать ваш родительский метод во внутренний класс, если статическая переменная может каким-то образом получить информацию о вызывающем абоненте.

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
    }
}
  • Вы можете избежать жесткого кодирования имен методов в свойстве, используя некоторые трюки дерева выражений.

  • Вы можете избежать вспомогательного конструктора и сделать свойство статическим, но вам нужно получить информацию о вызывающем абоненте внутри свойства, используя StackTrace.

Наконец, внутри метода всегда существует const, но тогда один, это не переменный, два, только константы времени компиляции разрешены. Просто заявляю.

Ответ 12

Я разработал статический класс, который справляется с этой проблемой довольно простым образом:

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;

}

Он может быть реализован в рамках метода, подобного этому:

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

При каждом последующем вызове метода значение, содержащееся в myVar.Value, будет последним, когда оно было установлено так, чтобы повторные вызовы вызывали его для вывода последовательности натуральных чисел. Функция Init() устанавливает значение только в том случае, если оно не было ранее инициализировано. В противном случае он просто возвращает ссылку на объект, содержащий значение.

Он использует атрибуты [CallerFilePath], [CallerMemberName] и [CallerLineNumber] для отслеживания того, на какой предмет ссылается словарь. Это устраняет возможность коллизий между методами с одинаковыми именами или вызовами из одних и тех же номеров строк.

Несколько предостережений об использовании:

  • Как утверждали другие, стоит подумать, нужно ли то, что вы делаете, для использования статических локальных переменных. Их использование иногда может быть признаком того, что ваш дизайн испорчен и может использовать некоторые рефакторинги.
  • Этот метод решения проблемы включает пару уровней косвенности, замедляющих выполнение вашей программы. Его следует использовать, только если это оправдывает эту стоимость.
  • Статические локальные переменные могут помочь вам справиться с наличием слишком большого числа членов, объявленных в вашем классе, и таким образом их разграничить, где они используются. Это должно быть сопоставимо с затратами времени исполнения, но иногда может стоить того. С другой стороны, наличие так много членов, объявленных в классе, может свидетельствовать о проблемах проектирования, которые стоит рассмотреть.
  • Поскольку эти значения продолжают оставаться в памяти после завершения их методов, вы должны помнить, что использование их для хранения больших фрагментов памяти предотвратит сбор мусора до завершения программы, что уменьшит ваши доступные ресурсы.

Этот подход, вероятно, будет излишним для большинства случаев, когда вы захотите использовать статические локальные переменные. Его использование косвенности для обработки отдельных файлов, методов и строк может быть ненужным для вашего проекта, и в этом случае вы можете упростить его для удовлетворения ваших потребностей.