C#是否支持使用静态局部变量?
相关: 如何在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或加载。 对?
不,C#不支持此function。 你可以靠近:
private static System.Text.RegularExpressions.Regex re = new System.Text.RegularExpressions.Regex("\\(copy (\\d+)\\)$"); private static string AppendCopyToFileName(string f) { }
这里唯一的区别是’re’的可见性。 它不仅仅暴露于该方法。
在第一次以某种方式使用包含类时,将初始化re
变量。 所以把它放在一个专门的小class上。
很不幸的是,不行。 我真的很喜欢这种可能性。
我知道你能做什么。
创建一个类,该类将提供对特定于实例的值的访问,这些值将静态保留。
像这样的东西:
class MyStaticInt { // Static storage private static Dictionary staticData = new Dictionary (); 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。
为什么不在类上创建static readonly
成员并在静态构造函数中初始化它呢?
这将为您带来相同的性能优势 – 它只会初始化一次。
不在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的原因。
这个怎么样,因为你只想在它被使用时进行初始化:
private static System.Text.RegularExpressions.Regex myReg = null; public static void myMethod() { if (myReg == null) myReg = new Regex("\\(copy (\\d+)\\)$"); }
C#不支持静态局部变量。 除了上面发布的内容之外,还有一个关于该主题的MSDN博客条目的链接:
http://blogs.msdn.com/b/csharpfaq/archive/2004/05/11/why-doesn-tc-support-static-method-variables.aspx
按照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来强制重新初始化运行时常量值。
当然。 您只需要在方法之外声明私有静态变量。
private static readonly System.Text.RegularExpressions.Regex re = new System.Text.RegularExpressions.Regex( "\\(copy (\\d+)\\)$" ); private static string AppendCopyToFileName( string f ) { //do stuff. }
这实际上就是你正在做的事情,唯一的区别是“re”对整个类的可见性而不仅仅是方法。
我还没有看到一个很好的通用解决方案,所以我想我会想出自己的解决方案。 但是我应该注意到,在大多数情况下(并非总是)需要静态局部变量可能表明你应该根据许多人所说的原因重构你的代码; state是对象而不是方法的东西。 但我喜欢限制变量范围的想法。
无需再费周折:
public class StaticLocalVariable { private static Dictionary s_GlobalStates = new Dictionary(); 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 intTest1 = new StaticLocalVariable (0); StaticLocalVariable intTest2 = new StaticLocalVariable (1); StaticLocalVariable doubleTest1 = new StaticLocalVariable (2.1); StaticLocalVariable doubleTest2 = new StaticLocalVariable (); 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
如您所示,将相关成员嵌套在内部类中最可能是最干净的。 如果静态变量可以某种方式获取调用者信息,则无需将父方法推送到内部类。
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
可能,但是一个,它不是变量,两个,只允许编译时常量。 只是陈述。
三年后…
您可以使用捕获的局部变量对其进行近似。
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 CreatePicker() { int boog = 0; return () => boog++; } }
我开发了一个静态类,以一种相当简单的方式处理这个问题:
using System.Collections.Generic; using System.Runtime.CompilerServices; public static class StaticLocal { static StaticLocal() { dictionary = new Dictionary>(); } 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()); if (!dictionary[lineNumber].ContainsKey(secondKey)) dictionary[lineNumber].Add(secondKey, new Access(value)); return dictionary[lineNumber][secondKey]; } private static Dictionary> dictionary; }
它可以在这样的方法中实现:
var myVar = StaticLocal.Init(1); Console.Writeline(++myVar.Value);
在每次对该方法的后续调用中,myVar.Value中包含的值将是它设置的最后一个值,因此重复调用将导致它输出一系列自然数。 Init()函数仅在先前未初始化时设置该值。 否则,它只返回对包含该值的对象的引用。
它利用[CallerFilePath],[CallerMemberName]和[CallerLineNumber]属性来跟踪字典中的哪个项目被引用。 这消除了具有相同名称的方法或来自相同行号的调用之间发生冲突的可能性。
关于其用法的一些注意事项:
- 正如其他人所说的那样,值得考虑一下你所做的事情是否真的需要使用静态局部变量。 它们的使用有时可能表明您的设计存在缺陷,并且可能会使用一些重构。
- 这种处理问题的方法涉及几层间接,从而减慢了程序的执行速度。 只有在certificate成本合理时才应该使用它。
- 静态局部变量可以帮助您处理在您的类中声明的成员太多,从而将它们划分为使用它们的位置。 这应该与执行时间成本进行权衡,但有时可能值得。 另一方面,在课堂上宣布这么多成员可能是值得考虑的设计问题的指示。
- 因为这些值在方法完成执行后继续保留在内存中,所以必须注意使用它们来存储大块内存将阻止垃圾收集,直到程序完成,从而减少可用资源。
对于大多数想要使用静态局部变量的实例,这种方法可能有点过分。 它可能不需要使用间接来处理单独的文件,方法和行,在这种情况下,您可以简化它以满足您的需求。