使用私有静态方法的优点


209

当创建一个具有内部私有方法的类(通常是为了减少代码重复)而又不需要使用任何实例字段时,将该方法声明为静态方法是否具有性能或内存优势?

例:

foreach (XmlElement element in xmlDoc.DocumentElement.SelectNodes("sample"))
{
    string first = GetInnerXml(element, ".//first");
    string second = GetInnerXml(element, ".//second");
    string third = GetInnerXml(element, ".//third");
}

...

private static string GetInnerXml(XmlElement element, string nodeName)
{
    return GetInnerXml(element, nodeName, null);
}

private static string GetInnerXml(XmlElement element, string nodeName, string defaultValue)
{
    XmlNode node = element.SelectSingleNode(nodeName);
    return node == null ? defaultValue : node.InnerXml;
}

将GetInnerXml()方法声明为静态方法有什么好处?请没有意见回应,我有意见。


1
可以将Method的
drzaus

Answers:


221

FxCop规则页面上:

将方法标记为静态后,编译器将向这些成员发出非虚拟调用站点。发出非虚拟调用站点将阻止在运行时检查每个调用,以确保当前对象指针为非空。对于性能敏感的代码,这可以导致可衡量的性能提升。在某些情况下,无法访问当前对象实例表示正确性问题。


37
我还要补充一点,“静态”子句不会造成损害,并且已经提供了一些带有“ 1”字的“文档”。它告诉您该方法未使用任何实例成员,并且您几乎免费获得此文档
frandevel 2014年

20
我要说的是:“如果方法不需要状态访问(此),则使其成为静态”是一般规则。
DanMan 2015年

3
为了平衡起见,值得指出的是,很多人通常反对使用静态方法,因为它们破坏了多态性,并且意味着该对象无法存根进行测试。例如,请参见googletesting.blogspot.co.uk/2008/12/…–
安迪

@Andy-好点。画线的一种方法是查看静态方法是否正在访问您传入的参数之外的任何东西。只要它以这种方式是自包含的,它就应该易于测试,并且不需要存根任何东西。
尼尔

4
许多开发人员不熟悉“私有静态”。我在团队的通用代码库中使用了它,并导致混乱。作为回报,它提供的好处很小。我们可以选择教育团队中的每个人,包括将来维护代码的所有开发人员,这意味着什么。但是将私有方法切换到私有静态方法的好处很小(即消除对实例数据的依赖),这不值得付出努力和困惑。无论哪种方式,该方法都已经是专用的。这是一种不需要真正了解的语言怪癖。
柯蒂斯·雅洛普

93

在编写类时,大多数方法分为两类:

  • 使用/更改当前实例状态的方法。
  • 不使用/更改当前对象状态的辅助方法,但可以帮助我计算其他地方需要的值。

静态方法很有用,因为仅通过查看其签名即可知道,调用它不会使用或修改当前实例的状态。

举个例子:

公共课图书馆
{
    私人静态书findBook(List <Book>书籍,字符串标题)
    {
        //代码在这里
    }
}

如果某个库的状态实例被搞砸了,而我试图找出原因,那么可以从其签名中排除findBook作为罪魁祸首。

我尝试与方法或函数的签名进行尽可能多的交流,这是实现此目的的绝佳方法。


1
是C ++中的一种const-method声明,不是吗?
anhoppe

是的-这是使用该语言通过限制可能出问题的地方来简化事情的另一种好方法。
尼尔

这不一定是正确的。让我们假设Library有一个实例字段List<Book> _books来存储它的书(不是如何你设计一个Library类可能,但W / E),并把它传递这份名单findBook,而静态方法调用books.Clear()books.Reverse()等等。如果您给静态方法授予对某些可变状态的引用的访问权限,则该静态方法很可能会弄乱您的状态。
萨拉

1
真正。在那种情况下,签名将向我们表明该方法可以访问(并且具有对Library实例进行更改的能力)。
尼尔,

实际上,对于我们可能使用的任何保护结构,都有某种方法可以破坏它。但是使用它们仍然是明智的选择,可以帮助我们朝着正确的方向前进,朝着“成功之坑”迈进。
尼尔,


15

是的,编译器不需要将隐式this指针传递给static方法。即使您没有在实例方法中使用它,它仍然会被传递。


这与运行时的性能或内存优势有何关系?
Scott Dorman

11
传递额外的参数意味着CPU必须做额外的工作才能将该参数放置在寄存器中,如果实例方法调出另一个方法,则将其压入堆栈。
肯特·布加亚特

5

由于没有传递此参数,因此速度会稍快一些(尽管调用该方法的性能成本可能比节省的成本高得多)。

我想说的是,对于私有静态方法而言,最好的原因是它意味着您不会偶然更改对象(因为没有此指针)。


4

这迫使您记住还必须声明该函数使用的所有类作用域成员都为静态成员,这应节省为每个实例创建这些项的内存。


仅仅因为它是一个类范围的变量并不意味着它应该是静态的。
Scott Dorman

3
不可以,但是如果它是由静态方法使用的,则它必须是静态的。如果该方法不是静态的,则可能没有使类成员成为静态的,这将导致为该类的每个实例使用更多的内存。
Joel Coehoorn

2

我非常希望所有私有方法都是静态的,除非它们确实不是静态的。我更喜欢以下内容:

public class MyClass
{
    private readonly MyDependency _dependency;

    public MyClass(MyDependency dependency)
    {
        _dependency = dependency;
    }

    public int CalculateHardStuff()
    {
        var intermediate = StepOne(_dependency);
        return StepTwo(intermediate);
    }

    private static int StepOne(MyDependency dependency)
    {
        return dependency.GetFirst3Primes().Sum();
    }

    private static int StepTwo(int intermediate)
    {
        return (intermediate + 5)/4;
    }
}

public class MyDependency
{
    public IEnumerable<int> GetFirst3Primes()
    {
        yield return 2;
        yield return 3;
        yield return 5;
    }
}

所有访问实例字段的方法。为什么是这样?因为随着这种计算过程变得越来越复杂,并且该类最终使用15个私有帮助器方法,所以我真的希望能够将它们拉入一个新类,该新类以语义上有意义的方式封装步骤的子集。

MyClass由于需要日志记录并且还需要通知Web服务而获得更多依赖关系时(请原谅示例),那么轻松查看哪些方法具有哪些依赖关系确实很有帮助。

诸如R#之类的工具使您可以通过几次击键从一组私有静态方法中提取一个类。当所有私有帮助程序方法都与实例字段紧密耦合时,请尝试执行此操作,您会发现这很头疼。


-3

如前所述,静态方法有很多优点。然而; 请记住,它们将在应用程序的生命周期内始终存在。我最近花了一天的时间来跟踪Windows Service中的内存泄漏...该泄漏是由实现IDisposable的类中的私有静态方法引起的,并且始终通过using语句进行调用。每次创建此类时,不幸的是,堆中为该类中的静态方法保留了内存,但是,当该类被处置时,静态方法的内存并未释放。这导致该服务的内存占用量在几天之内消耗了服务器的可用内存,并产生了可预测的结果。


4
这没有任何意义。堆从不任何静态或其他方法的代码存储内存。堆用于对象实例。任何方法的任何调用都将在堆栈上存储数据(以保存参数,返回值,未悬挂的本地变量等的内存),但是当方法完成执行时,所有这些都将消失。
Servy
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.