何时将整个类声明为静态

3
我有一个数学帮助类,在这个类中,每个函数都是静态的,也就是说,参数以参数形式输入,值以返回值形式返回。我应该将整个类声明为静态的吗?将静态修饰符添加到类中是否会影响性能?
此外,我不确定在 "不要将静态类视为杂项存储桶" 中 这个指南 的含义是什么——我有一些只是一堆零散静态函数的类...
4个回答

6

将类设置为static是完全可以的,在事实上,如果你查看System.Math,你会发现它也是static的:

public static class Math

这个指南想要表达的是,你不应该把所有的静态方法都放在一个静态类中,让它承担所有任务并充当静态方法的桶。相反,如果适合的话,应该创建一些较小的util类,其中包含与同一功能相关的方法,就像在BCL中的System.Math和其他几个类中所做的那样。


1

我应该将整个类声明为静态的吗?

是的。在一个类中添加 static 表示它只包含静态成员,你不能实例化它。如果没有它,使用你的类的用户可能会感到困惑,并尝试创建类的实例或变量。有了 static,这是不可能的。

看起来这正是你的情况。

将 static 修饰符添加到类中是否会影响性能?

不会,对静态方法的调用始终具有相同的性能特征,无论包含类是否为 static。实际上,在 CIL 级别上不存在静态类的整个概念,它们只是密封的抽象类(这种组合在 C# 中无法编译)。

但即使有差异,也非常微小。不要过早优化,特别是当涉及微观优化时。


除了.NET环境,这在其他环境中是否有所不同? - ina
@ina 你在说哪些其他环境?我想不出还有哪个主要语言支持静态类(肯定不包括C++或Java)。 - svick

0

一切都始于何时应该使用静态方法,那就是当您没有任何依赖于实例变量时。

现在说了这些,如果您的所有方法都不依赖于实例变量,则可以将类设置为静态。

静态类提供了多种好处,还有更多。


0

辅助类通常是静态类,因此您不需要实例化它们。实例化托管的.NET对象(特别是辅助类)并没有太大的成本,这只是一种方便。

很容易就可以组合一个带有最小辅助方法的静态类并完成工作。它们在代码中有其位置,并且可以在确定性输入/输出时使用,例如字符串的ComputeHash,数字的Find Average等。

但是,静态类被反对的一个原因是它们通常会干扰单元测试并引起各种问题。(Fakes,Moles,Private Accessors等)

即使是辅助类,基于接口的方法也有助于整个代码的单元测试。这对于涉及工作流程的大型项目尤其如此,其中静态辅助方法仅是工作流程的一部分。

例如,假设您需要检查当前年份是否为闰年。编写一个快速的静态方法是很诱人的。

public static class DateHelper
{
 public static bool IsLeapYear()
 {
  var currentDate = DateTime.UtcNow;
  // check if currentDate's year is a leap year using some unicorn logic
  return true; // or false
 }
}

如果你的代码中有类似这样的方法:

public class Birthday
{
 public int GetLeapYearDaysData()
 {
   // some self-logic..

   // now call our static method
   var isLeapYear = DateHelper.IsLeapYear();

   // based on this value, you might return 100 or 200.

   if (isLeapYear)
   {
    return 100;
   }

   return 200;
 }
}

现在,如果你尝试对这个方法 public int GetLeapYearDaysData() 进行单元测试,你可能会遇到麻烦,因为返回值是不确定的,即取决于当前年份,而且不建议让单元测试的行为随着时间的推移变得不可预测或恶化。

// this unit test is flaky
[Test]
public void TestGetLeapYearDaysData()
{
 var expected = 100;

 // we don't know if this method will return 100 or 200.
 var actual = new Birthday().GetLeapYearDaysData();

 Assert.AreEqual(expected, actual);
}

以上问题是由于我们无法控制/模拟上面代码中的IsLeapYear()方法而导致的。因此,我们要听从它的意愿。
现在想象一下以下的设计:
public interface IDateHelper
{
 bool IsLeapYear();
}

public class DateHelper : IDateHelper
{
 public bool IsLeapYear()
 {
  var currentDate = DateTime.UtcNow;
  // check if currentDate's year is a leap year using some unicorn logic
  return true; // or false
 }
}

现在我们的生日类可以注入一个帮助器:
public class Birthday
{
 private IDateHelper _dateHelper;

 // any caller can inject their own version of dateHelper.
 public Birthday(IDateHelper dateHelper)
 {
  this._dateHelper = dateHelper;
 }

 public int GetLeapYearDaysData()
 {
   // some self-logic..

   // now call our injected helper's method.
   var isLeapYear = this._dateHelper.IsLeapYear();

   // based on this value, you might return 100 or 200.

   if (isLeapYear)
   {
    return 100;
   }

   return 200;
 }
}

// now see how are unit tests can be more robust and reliable

// this unit test is more robust
[Test]
public void TestGetLeapYearDaysData()
{
 var expected = 100;

 // use any mocking framework or stubbed class
 // to reliably tell the unit test that 100 needs to be returned.

 var mockDateHelper = new Mock<IDateHelper>();

 // make the mock helper return true for leap year check.
 // we're no longer at the mercy of current date time.

 mockDateHelper.Setup(m=>m.IsLeapYear()).Returns(true);

 // inject this mock DateHelper in our BirthDay class
 // we know for sure the value that'll be returned.
 var actual = new Birthday(mockDateHelper).GetLeapYearDaysData();

 Assert.AreEqual(expected, actual);
}

正如您所看到的,一旦辅助方法基于接口,它们就很容易进行测试。在一个大项目的过程中,许多这样的较小静态方法最终会导致测试关键功能流程的瓶颈。

因此,提前意识到这个陷阱并进行额外的投资是值得的。基本上,要确定哪些类/方法需要是静态的,哪些不需要。


什么是接口化方法(与静态方法相对)用于辅助类?静态类会如何干扰单元测试? - ina

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