了解static关键字


16

我有一些使用Java,Javascript和PHP开发的经验。

我正在阅读Microsoft Visual C#2010分步教程,我认为这是一本关于向您介绍C#语言的很好的书。

我在理解static关键字时似乎遇到了问题。据我了解,如果将一个类声明为静态,则所有方法和变量都必须为静态。main方法始终是静态方法,因此在类中,main方法存在所有变量,并且如果必须在main方法中调用它们,则将这些方法声明为静态。我还注意到,为了从另一个类调用静态方法,您不需要创建可以使用类名的对象。

但是static关键字的实际目的是什么?什么时候应该声明静态变量和方法?


4
C#中的static与Java中的static几乎相同。如果您了解Java,则在C#中不会有任何问题
superM 2012年

Java是我的第一个编程语言,我不理解这个概念有either.I只用Java进行的短时间
尼斯特尔亚历

简而言之:当您不需要面向对象(例如,仅一些独立的方法或变量)时,请使用“静态”。声明一个类为静态意味着将那些非面向对象的函数和变量放在一个通用名称(空间)中,即类名称。
Doc Brown

Answers:


15

C#中的'static'关键字引用该类中的某些内容,或该类本身,该内容在该类的所有实例之间共享。例如,可以通过类名从该类的所有实例访问标记为静态的字段。

public class SomeObject
{
    //Static Field
    static int Foo = 3;

    //instance field
    private int _Foo2 = 4;

    //instance property
    public int Foo2{get{return _Foo2;}set{_Foo2 = value;}}


    //static factory method
    public static SomeObject CreateSomeObject(int fooValue)
    {
        SomeObject retVal = new SomeObject();
        retVal.Foo2 = fooValue;
        return retVal;
    }

    //Parameterless instance constructor
    public SomeObject()
    {
    }

    public static int Add(int x)
    {
        //Static methods can only deal with local variables, or fields that
        //  are also static in the class.  This one adds x to the static member foo
        return x + Foo;

        //Foo2 is not accessable here!
    }

      //Instance method
    public int AddSomething(int x)
    {
        //Add x to the property value of Foo2
        return x + this.Foo2;

        //Note that Foo *is* accessable here as 'SomeObject.Foo'
    }

}

老实说,除创建扩展方法(扩展方法快速教程)外,我从未使用过标记为static的类。

无论如何,都有一些使用静态方法的特定设计模式,例如工厂模式单例模式,但是要记住的重要一点是,静态方法和构造函数不会处理类的任何特定实例(除非您传入一个实例),通常进行计算或在对象之间进行比较。您所引用的“ Main”方法始终是静态的,但是要从不同的角度来看它,请参阅本文

为此,这里介绍了如何调用静态方法和实例化方法,字段和属性之间的差异。

public static void Main(string[] args)
{
    //This is a static method that starts a thread in an application
    // space.  At this point not everything actually has to be static...

    //Here is an instantiation with a parameterless contruction
    SomeObject obj = new SomeObject();

    //Here is an instantiation using a static factory method
    SomeObject obj2 = SomeObject.CreateSomeObject(3);

    //Getting field value from static field
    // Notice that this references the class name, not an instance
    int fooValue1 = SomeObject.Foo;

    //Getting property value from instance
    //  Note that this references an object instance
    int fooValue2 = obj2.Foo2;

    //Instance method must be called through an object
    obj2.AddSomething(4);  //if default constructor, would return 8

    //Static methods must be called through class name
    SomeObject.Add(4); //Returns 7
}

另外,请查看这篇文章,以更深入地了解静态类。


18

这是约书亚·布洛赫(Joshua Bloch)的解释方式,就像他所说的大多数话一样,我发现他很聪明(是的,我是约书亚·布洛赫的粉丝男孩:)。这是从内存中引用的。

想象一下,一堂课相当于一栋房子的蓝图。想象一下,房子是蓝图,因为该类的实例是该类的实例。您可以从中创建一个类(蓝图)和多个实例(房屋)。

现在,常识表明,房屋(实例)可以具有/执行的大多数功能/行为,即使它们是在蓝图中声明的,也要等到真正的房屋(实例)由蓝色制成后才能使用-print(类)。就像,您的蓝图可能在其中包含了电灯开关和灯泡应该去的地方,但是您无法使这些图在蓝图上起作用,因此您必须实际建造房屋才能打开和关闭电灯开关,并让某些灯泡打开和关闭。

但是,您可能会有一些行为直接适用于该蓝图,并且您可以直接在该蓝图上使用/访问该行为,而无需从该蓝图上建造一个实际房屋。想象一下,您的蓝图有一个按钮,按下该按钮将显示该蓝图中包含的房屋的占地面积(通过计算墙壁等的所有长度)。显然,您可以先建一栋房子,然后再四处测量其占地面积,但是您可以仅凭蓝图进行此操作,因此在蓝图中实现此行为会更有帮助。这种用于计算房屋占地面积的蓝图嵌入式按钮等效于在类中具有静态功能。


或者,您将拥有一个Blueprint实现蓝图功能的类,包括计算蓝图表示的房屋占地面积的功能。然后将此蓝图实例馈送到一个Builder(本身可能是一个实例),后者又Building根据蓝图执行构造和输出可能任意数量的实例所需的操作。
CVn

12

以这种方式查看它对我有帮助:

  • 每个类型都有一个静态实例。
  • 静态实例是在您首次访问类型时创建的-通过静态实例或创建另一个实例。
  • 您可以根据需要创建任意数量的非静态实例,但是只有一个静态实例。
  • 类中声明为static的任何内容都属于静态实例,因此无权访问您创建的任何其他实例。但是其他实例确实可以访问静态实例。
  • 如果将一个类声明为静态类,则您将无法创建其他实例,只有静态实例可以存在。
  • 您可以为静态实例声明一个静态构造函数,就像为普通实例构造一个声明一样(但要声明为静态)。

至于何时使用static关键字:

  • 任何不需要访问本地属性的方法都可以并且可能应该声明为静态。
  • 可以将任何状态都没有的辅助类(无论如何应该很少)并且永远不会被嘲笑的辅助类可以声明为静态的。他们是否应该是另一回事。谨慎使用此功能。
  • 必须由类的所有实例访问的属性和字段必须声明为静态。但是,仅在没有其他选择时才使用此选项。

+1,作为一个很好的总结,我不知道每个类型都有1个静态实例,我发现告诉您真相很奇怪。
NoChance 2012年

2
@EmmadKareem那只是pdr使用的一种心理模型,因为"Looking at it this way helps"他。您会发现它很奇怪,因为它并不完全正确,但是如果您愿意,可以这样想。你知道玻尔模型吗?它提出了一组规则和想法,原子和电子如何相互作用。该模型的工作取决于您执行的操作,但事实并非如此。
phant0m 2012年

@ phant0m,感谢您的解释,我给我的印象是它确实不是模型,因此我感到惊讶。
NoChance 2012年

实际上,有时由于某些原因,static即使不使用本地属性,您可能也不想创建方法。制作事物static会增加与客户端的耦合,因为它们必须直接解决类。例如,这可能会使带有模拟的单元测试更加困难。
艾伦2014年

@Allan:可以说,如果您要在不影响该类实例状态的类上调用公共方法,则该方法应该是静态的,以使客户端开发人员可以清楚地知道。如果该方法做得太多以至于需要模拟,那就是一个不同的问题,可以通过许多不同的方法来解决。
pdr 2014年

4

最简单的解释---静态=>每个环境仅存在一个副本。

因此,在VM或CLR中,将永远只有一个静态类的副本,并且引用它的任何其他类将必须与引用它的所有其他类共享其方法和数据。

对于静态变量,在运行时环境中,无论引用了静态变量时创建了拥有类的多少个副本,它们都将引用相同的存储空间,因此在运行时环境中将只有该实例的一个实例。


2

静态成员与该类相关联,而不与该类的任何实例相关联。

由于我们在谈论.Net,因此请考虑String类,尤其是SplitJoin方法。

拆分是一个实例方法。创建一个String变量,给它一个值,您可以在该变量/值上调用Split()并返回一个“ bits”数组:

String s1 = "abc,def,ghi" ; 
String[] array2 = s1.Split( ',' ) ; 

因此,对于实例方法,在给定的类实例内保存的值很重要

Join是一种静态方法。好的,给定分隔符和String数组时,它会产生String 结果,因此它与String类“有关系”,但它与任何String实例中的任何特定都不相关(实际上,实例值是不适用于静态方法)。 在其他语言中,Join方法可能已经“粘在” Array类(或者可能更好的是StringArray类)上,但是我们在Redmond的Friends认为它与String类更“相关”,因此他们将其放在那里。

String[] array3 = { ... } 
s1 = String.Join( array3, "," ) ; 

另一个替代方法可能是有一个实例 Join方法,其中保存在String [类实例]中的值用作连接分隔符,例如:

// Maybe one day ... 
String s4 = "," ; 
s1 = s4.Join( array3 ) ; 

2

static关键字可以是一个有点难以新手掌握。其主要目的是将一个类成员标识为不属于该类的任何单个实例,而是属于该类本身。

无需赘述,C#(和Java)严格执行面向对象的理想,即所有代码和数据都必须属于一个对象,因此在范围,可见性和生存期方面受到限制。通常,这是表示某个现实事物的对象的基本原则适用的最佳实践。但是,并非总是如此。有时,您需要的是可以从代码中的任何位置访问的函数或变量,而无需让您传递对包含该对象的对象的引用,并保证您正在查看或更改的数据正是每个人都可以使用的else正在处理,而不是属于对象的不同实例的副本。

在C和C ++中,这种行为以“全局”函数或变量的形式出现,但没有封装在对象中。因此,作为一种折衷,C#和Java支持“静态作用域”,这是没有父对象的真实全局代码与有限范围的实例成员之间的中间点。

static从程序main()功能的第一行开始,声明为的任何“代码成员”(功能,属性,字段)都在范围之内,并且直到该main()功能终止后才离开。用简单的英语来说,存在静态成员,并且只要程序正在运行就可以使用。此外,通过将静态成员称为类型本身的成员而不是该类型的任何一个实例的成员来调用静态成员:

public class Foo
{
   public int MyInt {get;set;} //this is an "instance member"
   public static int MyStaticInt {get;set;} //this is a "static member"
}

...

var myFoo = new Foo();
myFoo.MyInt = 5; //valid
myFoo.MyStaticInt = 5; //invalid; MyStaticInt doesn't belong to any one Foo

Foo.MyInt = 5; //invalid; MyInt only has meaning in the context of an instance
Foo.MyStaticInt = 2; //valid

这使静态成员对任何知道类型的代码都可见,无论他们是否知道该类型的任何单个实例。

要回答您的问题,将某物标记为静态的主要好处是,无论使用哪种类型的代码,无论使用的代码是否具有或可以获取包含对象的实例,它都可以在任何类型的已知位置都可见。还有一点性能上的好处;因为该方法在静态范围内,所以它只能访问(同一类或其他类的)其他静态成员,以及任何作为参数传入的成员。因此,运行时不必解析对包含对象的当前实例的任何引用,就像通常为提供特定于上下文的状态信息的实例方法所必需的那样。

整个类也可以标记为静态。这样,您告诉编译器类声明将仅由静态成员组成,因此无法实例化。这是一种确保内存中对象只有一个且只有一个副本的简便方法。使类及其中的所有内容保持静态。但是,很少有这是满足这种需求的最佳解决方案。在只需要一组数据的一个副本的情况下,通常提倡使用“单例”。这是一个非静态类,它使用静态访问器和非公共构造函数来提供对自身单个实例的访问。从理论上讲,单例可以提供与完全静态类几乎相同的好处,但具有以基于实例,面向对象的方式使用该类的附加功能。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.