为什么C#不像C一样支持局部静态变量?

92

C#为什么没有像C语言一样的本地静态变量呢?我很想念它!!


5
你需要问设计师。 - John Saunders
10
小知识:VB.NET 使用 [Static] 关键字来支持这一点。 - vcsjones
4
这不仅仅是琐事 - 似乎语言设计者得出了相反的结论。了解其中原因会很有趣。 - Jon
2
我知道C#中static的含义...我的问题是在C语言中使用static。 - JoelFan
2
@Atomosk 任何你希望函数每次加载时都保持相同值的地方,而不必将其暴露给整个类。 - AustinWBryan
显示剩余8条评论
13个回答

180
因为他们搞砸了,而且为了符合自己的需求而遗漏了一个有用的功能。
所有关于编码方式、智能性以及您应该重新考虑生活方式的争论,都是自大的防御性借口。
当然,C#纯粹、面向对象。这就是为什么它们会为lambda函数自动生成持久化本地变量。一切都很复杂。我感觉自己很傻。
在许多情况下,循环作用域静态很有用且重要。
简短而真实的回答是,您必须将局部静态变量移动到类作用域中,并接受C#中的命名空间污染。把你的投诉带到市政厅。

79
大多数答案都以“你可以通过……来实现这一点”结束,但这样并不足够。我可以通过许多方式实现所需的行为,但这并不是重点。静态局部变量与静态类变量完全相同,但可见性较低,这正是所需的行为。 - Adam Naylor
5
在.NET中实现此功能是可行的,只是C#不支持。例如,VB.NET支持局部静态变量。 - Stefan Steiger
8
实际上,VB.NET只是在幕后创建静态的类级别变量。CLR不支持静态方法变量。 - JDB
12
每当你说“你能做的任何事情...你也可以做”时,必须以“那你为什么不编写图灵机程序呢?”结束。将变量范围与生命周期分开是非常有用的能力,静态实例字段不能阻止其他开发人员在不应该更改时进行篡改 - 否则,为什么要使用公共/私有等修饰符呢? - NetMage
7
我希望我能给这个答案点上几千个赞。对我来说,它不仅仅是关于防篡改,更多的是为了明确目的。为了未来的我,比如明天或者今天晚些时候。有时候(通常情况下是不可能的)很难定义某个东西以便立即清楚它的所有方面。在这种情况下,限定范围是极其有用的。非常非常有用。没有足够好的理由不使用范围受限的静态变量。是的,有时你可能会意识到需要在函数外访问它。因此,如果/当发生这种情况,重构并给它一个更大的名称、更多的文档和更大的作用域。 - Shavais
显示剩余3条评论

47

这篇来自2004年的MSDN博客文章:为什么C#不支持静态方法变量?回答了原帖中提出的确切问题:

C#不支持这个特性有两个原因。

首先,通过拥有类级别的静态变量,几乎可以达到相同的效果,而添加方法级别的静态变量会增加复杂性。

其次,方法级别的静态变量在重复调用或从多个线程调用代码时可能会引起问题,并且由于定义在方法中,很难找到定义。

[作者:Eric Gunnerson]

同一篇博客文章在微软的自己的存档中。Archive.org保留了评论。微软的存档没有。)


32
我知道这不一定是你的个人观点,但有人可能会为类级别静态变量做出完全相同的论证。 “多线程很难且容易出错”以及“多个用户在内存中修改完全相同的静态值更容易出错且难以追踪”,这并不是不包括此功能的借口。 这些都是现有功能集中存在的问题,而这种功能并不会特别恶化这些问题。 他们有静态类和静态构造函数,这些甚至更加出名因为它们非常难以跟踪,使用单元测试更加困难! - Gurgadurgen

32

状态通常是对象的一部分或类型的一部分,而不是方法的一部分。(当然,被捕获变量是例外。)

如果你想要一个局部静态变量的等效物,请创建一个实例变量或一个静态变量,并考虑该方法本身是否应该成为具有该状态的不同类型的一部分。


5
请注意,在C++中,您可以使用静态“变量”来表示常量值。由于C#仅支持数学意义上的常量,因此处理本地计算的常量(例如2的平方根,仅为示例)会变得非常繁琐。 - greenoldman
6
这两个选项都是解决办法,因为它们比本地函数具有更广泛的可见性。创建一个新类是“更干净”的解决方案,但现在你需要更多的代码来解决缺失的特性。 - Jon
4
@JimBalter:我仍然不同意。状态与类型固有地相关联(例如,在类型初始化之前,它不存在,而在AppDomain存在期间它也存在)。不过,我希望反对意见能够表达时不要进行人身攻击... - Jon Skeet
3
仅仅是想说,我处于这样一种情况:我需要一个静态变量,它只会在一个方法中使用,但是在类定义中创建该变量会导致(不期望的)混乱代码。 - MasterMastic
3
@Ken: 但我的观点是这本身就是一种设计上的不良迹象。如果编译器支持这样做,它将不得不将变量提升为实例或静态变量 - 因此它仍然会成为您的类或实例状态的一部分。如果这对于您的类来说不合适,那么无论在源代码中如何表示,它都是不合适的。如果确实合适,为什么要隐藏那个状态? - Jon Skeet
显示剩余13条评论

21

我对 C 了解不多,相比之下更熟悉 C# ,但我认为你可以通过使用仅用于一个方法的类级别静态变量来完成与本地静态变量相同的所有功能。显然,这需要一些语法上的改变,但我相信你可以得到所需的任何功能。

此外,Eric Lippert 在他的博客上经常回答这样的问题。通常是以这种方式回答:“我经常被问及‘为什么C#没有实现X功能?’答案总是一样的:因为没有人设计、规定、实现、测试、文档化和发布该功能。”基本上,他的回答通常归结为添加任何功能都需要花费成本,因此许多潜在的功能没有实现,因为它们在成本效益分析中没有得到积极方面的结果。


24
我自己说不出更好的话。 - Eric Lippert
2
使用类静态变量实现本地静态变量的问题在于,类名称空间会不必要地被污染。C#添加功能所需的资源需求问题是为什么大多数来自微软(和其他公司,咳咳,Sun/Oracle)的语言并不出色的原因。它们毫不掩饰地做“足够好”的事情,而不是好的事情。 - demented hedgehog
3
我能够尊重这个答案的第二部分。虽然第一部分是一个解决方案,但并不是一个借口。这是重要的部分。正如其他人所说,说“通过做Y你可以完成X”与“你可以用图灵机模拟任何编程语言的所有功能”没有什么不同。然而我们不这样做,因为只有具备与图灵机相同的功能(当然除了无限存储器)的语言是不够高效的。 - Gurgadurgen
2
我相信你可以通过使用仅用于一个方法的类级静态变量来完成本地静态变量所能实现的一切。-- 草人。每个人都知道这一点,但它完全无关紧要,因为它没有解决人们想要方法本地静态变量的原因 - 词法局部性是一个重要的原则,许多语法检查器都会强制执行它。第二段就像“virtus dormitiva”或“le meilleur des mondes possibles” - 通用的挥手之词,没有回答问题。(自 EL 离开团队以来,C# 已经添加了很多好的功能。) - Jim Balter
1
经常情况下,拥有一种语言特性实际上会减少需要完成的工作量,因为它已经与现有系统免费提供,而不拥有它则需要积极地去除它。在C#中静态局部变量的情况下,这种情况非常接近危险边缘,因为它已经得到了运行时的支持,在其他语言中也被广泛理解和使用。 - kaalus

3

所以您想在方法中使用静态局部变量?恭喜!您又迈向了成为一个“真正”的程序员的一步。

不要听那些告诉您静态局部变量不是“干净”的,会影响“可读性”并可能导致微妙且难以找到的“错误”的人。这是无稽之谈!他们这么说只是因为他们是“自命不凡”的程序员!他们中的许多人甚至在业余时间里玩弄某种奇怪的函数式编程语言。你能相信吗?一群时髦的人!

“真正”的程序员拥抱我称之为SDD的范式-“Side effect Driven Design”。以下是其中一些最重要的法律:

不要太过可预测!即使使用完全相同的参数调用,也永远不要从方法中两次返回相同的内容!

放弃纯洁 - 让我们变得“肮脏”起来吧!状态本质上渴望改变,因为它是范畴论中多元态函子的一个无法满足的单子, 也就是说,它希望尽可能多地与协作者互动。永远不要错过这样的机会,让它感到高兴!

在编写副作用驱动代码时使用的工具之一是静态局部变量。然而,正如您所注意到的,C#并不支持它们。为什么?因为在过去的二十年中,微软已经被所谓的清洁代码入侵,他们更喜欢可维护性而非灵活性和控制。你还记得上一次看到我们心爱的蓝屏是什么时候吗?现在猜猜这是谁的错!

不过不用担心!“真正”的开发人员不必受那些糟糕的设计决策之苦。正如之前所提到的,借助于lambda表达式,可以实现一种类似静态的本地变量。
然而,提供的解决方案并不完全令人满意。使用先前的答案,我们几乎符合SDD标准的代码将会是这样的:
var inc = Increment();
var zero = inc();
var one = inc();

或者

var zero = Increment()();

但这只是愚蠢的。即使是一个想成为开发者的人也能看出Increment()不是一个普通的方法,并会感到怀疑。另一方面,一个真正的程序员可以使它更像SDD。他或她知道我们可以通过给属性或字段赋予类型Func<T>来使其看起来像一个方法!我们只需要执行一个lambda表达式来初始化它,该表达式再初始化计数器并返回另一个lambda表达式来递增捕获的计数器!

以下是适当的SDD代码:

public Func<int> Increment = new Func<Func<int>>(() =>
{
    var num = 0;
    return () => num++;
}).Invoke();

你是否认为上面的代码看起来有点像一个 IIFE?是的,你是对的,应该为自己感到羞愧。

现在每次调用 Increment() 它都会返回不同的内容

var zero = Increment();
var one = Increment();

当然,您也可以使得计数器在实例的生命周期内存活

这将展示给那些冒牌程序员们!


7
这个回答是认真的吗?感觉更像是一个古老的问题被用作发泄对“其他人”的差劲编程习惯的平台。 - Claies
1
@Claies,当然,这很严肃!这是唯一一个展示如何正确模拟静态局部变量的答案,因此从外部几乎无法区分常规C#方法和管理静态局部变量的C#“方法”之间的区别。 - Good Night Nerd Pride
1
这太有趣了!我从未想过可以像方法一样伪装属性... 可能是因为这是极少数需要这样做的情况之一。 - AustinWBryan
1
@GoodNightNerdPride 我已经尝试了你的解决方案,我真的很喜欢它。不幸的是,它对于引用类型(如List<>等,我将其初始化为null)无效。有没有办法让它工作?即使我在调用Func的方法中更改返回的列表,Func始终返回null。我以为引用会“粘住”,这样说。 - silkfire
1
@silkfire,所以你想从外部干预“静态局部变量”?这就是 SSD 精神!但开玩笑:C# 引用类型是对象的引用,而不是引用的引用!你基本上要做的就是这样:https://dotnetfiddle.net/wd73MS 请注意,第二个打印仍然显示旧计数,因为列表引用不能从外部重定向到新列表。但是,您可以通过调用 Add()Remove() 等方法来改变列表。 - Good Night Nerd Pride
显示剩余5条评论

2

C#是一种面向组件的语言,没有类或本地方法范围之外的变量概念。在方法内部声明的变量也不能被声明为静态的,这可能与您在C中习惯的做法不同。但是,您总可以使用类静态变量作为替代。

通常情况下,在C#中解决编程问题时,通常有其他方法而不必使用方法级静态变量。状态通常是应该设计到类和类型中而不是方法中的东西。


我必须说,拥有以下代码可能会非常好:lock(myLock){ ... }``` - Benji Altman

1

从逻辑上讲,是的。这与仅在该方法中使用的类级静态成员相同。但是,方法级静态成员将更具封装性。如果存储在成员中的数据仅应由单个方法使用,则应仅由该单个方法访问。

但是,在C#中,您可以通过创建嵌套类几乎完全实现相同的效果。


嵌套类可以访问其外部类的私有变量。然后,您可以使变量为私有,并将一个方法放在其中,但仍然需要将嵌套类的实例发送给它。当然,您还可以将其作为类扩展。这样,就没有人会知道了。 - Lee Louviere

0

我认为局部静态变量的想法可以通过创建公共静态字段来很容易地解决。逻辑上几乎没有什么改变,你不觉得吗?

如果您认为这将是一个重大的逻辑改变,我很愿意听听您的见解。

class MyClass
{
    public static float MaxDepthInches = 3;

    private void PickNose()
    {
        if (CurrentFingerDepth < MyClass.MaxDepthInches)
        {
            CurrentFingerDepth++;
        }
    }
}

7
这里有两个重要的逻辑变化:1)它是公共的,因此可以被类外的代码修改。这意味着使用你的类的其他人可能会以你的代码无法预测的方式修改它。不用说,这通常是不好的。2)变量的作用域是整个类而不仅仅是一个方法。即使你将其声明为私有,也将从一个方法的作用域更改为整个类的作用域,这是一个相当大的变化。 - reirab

0
因为静态局部变量与方法绑定,而该方法在所有实例之间共享。
我不得不纠正自己和其他程序员,他们期望它在使用该方法时对每个类实例都是唯一的。
然而,如果将其作为静态类或类的静态实例,则语法上清楚是否有一个实例每个容器类,或者根本没有一个实例。
如果您不使用这些,以后重构会更容易。

1
我认为“static”这个词本身应该表明只有一个变量。 - JoelFan
在极少数情况下,您需要在方法内部存储一个仅为1的变量,那么您必须要问自己为什么要将共享类状态信息分散到一堆方法中。 - Lee Louviere
1
李:通常是因为你正在处理可重入性或其他类似的方法特定问题。在这些情况下,逻辑上更合理的做法是将状态与方法关联起来,而不是与整个类关联,即使CLR与普通的私有静态变量实现方式在底层没有区别。 - reirab

0

你可以使用嵌套类来解决这个问题。由于C#将静态变量的作用域限制在类中,因此你可以使用嵌套类作为作用域。

例如:

public class Foo {
    public int Increment() {
        return IncrementInternal.Increment();
    }
    
    private static class IncrementInternal {
        private static int counter = 0;
        public static int Increment() {
            return counter++;
        }
    }
}

这里的Foo支持Increment方法,但它是通过私有嵌套类IncrementInternal来支持的,该类包含静态变量作为成员。当然,在Foo的上下文(其他方法)中,counter是不可见的。

顺便说一句,如果你想在IncrementInternal.Increment内部访问Foo的上下文(其他成员和方法),你可以在从Foo调用它时将this作为参数传递给IncrementInternal.Increment

为了尽可能地保持范围小,我的建议是为每个这样的方法创建一个嵌套类。因为这可能不是很常见,所以嵌套类的数量足够小,可以维护它。

我认为这比匿名函数或IIFE更清晰。

你可以在这里看到一个实时演示。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接