C#是否支持使用静态局部变量?

时间:2010-03-06 16:20:03

标签: c# static

  

相关:How do I create a static local variable in Java?


如果这是重复,请原谅;我很确定以前会问这个问题,我看了一下,但没有找到傻瓜。

我可以在C#中创建一个静态局部变量吗?如果是这样,怎么样?

我有一个很少使用的静态私有方法。静态方法使用正则表达式,我想初始化一次,并且仅在必要时。

在C中,我可以使用本地静态变量来完成此操作。我可以用C#做​​这个吗?

当我尝试编译此代码时:

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

...它给了我一个错误:

  

错误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或加载。正确?

12 个答案:

答案 0 :(得分:11)

不,C#不支持此功能。你可以接近:

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

private static string AppendCopyToFileName(string f)
{

}

这里唯一的区别是're'的可见性。它不仅仅暴露于该方法。

re变量将在第一次以某种方式使用包含类时初始化。所以把它放在一个专门的小班上。

答案 1 :(得分:5)

不幸的是,没有。我真的很喜欢这种可能性。

我知道你能做些什么。

创建一个类,该类将提供对特定于实例的值的访问,这些值将被静态保留。

这样的事情:

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

这不是一个完美的解决方案,而是一个有趣的玩具。

每个Namespace.Class.Method范围只能有一个这种类型的静态变量。不适用于属性方法 - 它们都解析为相同的名称 - get_InstanceId。

答案 2 :(得分:5)

为什么不在类上创建static readonly成员并在静态构造函数中初始化它呢?

这将为您带来相同的性能优势 - 它只会初始化一次。

答案 3 :(得分:4)

不在C#中,仅在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有许多C#没有的好东西,这就是我选择VB.NET的原因。

答案 4 :(得分:2)

这个怎么样,因为你只想在它被使用时进行初始化:

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

答案 5 :(得分:1)

C#不支持静态局部变量。除了上面发布的内容之外,还有一个关于该主题的MSDN博客条目的链接:
http://blogs.msdn.com/b/csharpfaq/archive/2004/05/11/why-doesn-t-c-support-static-method-variables.aspx

答案 6 :(得分:1)

按照Henk和BarretJ的回答,我认为您可以通过使用属性来避免初始化成本并且更加接近,

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

然后在代码中的任何地方使用MyReg(注意MyReg中的大写“M”)。关于这个解决方案的好处是(尽管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属性的后备存储),因为我只是不需要,记住这就像一个运行时常量,所以没有setter。但是,如果由于某种原因我需要更改运行时常量的值,我会直接在可空变量_BoolVar上执行此操作。

初始化可能非常复杂。但它只执行了一次,只在第一次访问该物业时才执行。您可以选择通过将_BoolVar设置为null来强制重新初始化运行时常量值。

答案 7 :(得分:0)

不确定。您只需要在方法之外声明私有静态变量。

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

这实际上是你正在做的事情,唯一的区别是“re”可以看到整个类,而不仅仅是方法。

答案 8 :(得分:0)

我还没有看到一个很好的通用解决方案,所以我想我会想出自己的解决方案。但是我应该注意到,在大多数情况下(并不总是)需要静态局部变量可能表明你应该根据许多人所说的原因重构你的代码;状态是对象而非方法的东西。但我喜欢限制变量范围的想法。

没有进一步的麻烦:

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

答案 9 :(得分:0)

三年后......

您可以使用捕获的局部变量对其进行近似。

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

答案 10 :(得分:0)

如您所示,将相关成员嵌套在内部类中最可能是最干净的。如果静态变量可以某种方式获取调用者信息,则无需将父方法推送到内部类中。

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. 您可以使用一些表达式树技巧避免在属性中对方法名称进行硬编码。

  2. 您可以避免使用辅助构造函数并使属性保持静态,但您需要使用StackTrace在属性中获取调用者信息。

  3. 最后,方法中总是有const,但是一个,它不是变量,两个,只允许编译时常量。只是陈述。

答案 11 :(得分:0)

我开发了一个静态类,以一种相当简单的方式处理这个问题:

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]属性来跟踪字典中的哪个项目被引用。这消除了具有相同名称的方法或来自相同行号的调用之间发生冲突的可能性。

关于其用法的一些注意事项:

  • 正如其他人所说的那样,值得考虑一下你所做的事情是否确实需要使用静态局部变量。它们的使用有时可能表明您的设计存在缺陷并且可能会使用一些重构。
  • 这种处理问题的方法涉及几层间接,从而减慢了程序的执行速度。只有在证明成本合理时才应该使用它。
  • 静态局部变量可以帮助您处理在您的类中声明过多的成员,从而将它们划分到他们使用的位置。这应该与执行时间成本进行权衡,但有时可能值得。另一方面,在课堂上宣布这么多成员可能表明设计问题值得考虑。
  • 因为这些值在方法完成执行后继续保留在内存中,所以必须注意使用它们来存储大块内存将阻止垃圾收集直到程序完成,从而减少可用资源。

对于大多数想要使用静态局部变量的实例,这种方法可能过度。它可能不需要使用间接来处理单独的文件,方法和行,在这种情况下,您可以简化它以满足您的需求。