C# 何时将整个类声明为静态类

C# 何时将整个类声明为静态类,c#,.net,oop,static-methods,static-classes,C#,.net,Oop,Static Methods,Static Classes,我有一个数学助手类,其中每个函数都是静态的,即作为参数输入的参数,返回的值。我应该将整个类声明为静态的吗?将静态修饰符添加到类中会对性能产生影响吗 此外,我不确定“不要将静态类视为杂项桶”中的含义-我有几个类只是一堆杂项静态函数 创建这样的类是非常好的静态的,事实上,如果你看一下,你会发现它也是静态的: public static class Math 该指南试图说明的是,您不应该将您拥有的所有静态方法都放在一个静态类中,该类将完成所有工作,并充当静态方法的bucket。相反,如果合适的话,可

我有一个数学助手类,其中每个函数都是静态的,即作为参数输入的参数,返回的值。我应该将整个类声明为静态的吗?将静态修饰符添加到类中会对性能产生影响吗


此外,我不确定“不要将静态类视为杂项桶”中的含义-我有几个类只是一堆杂项静态函数

创建这样的类是非常好的
静态的
,事实上,如果你看一下,你会发现它也是
静态的

public static class Math

该指南试图说明的是,您不应该将您拥有的所有静态方法都放在一个静态类中,该类将完成所有工作,并充当静态方法的bucket。相反,如果合适的话,可以使用与相同功能相关的方法创建较小的util类,就像使用
System.Math
一样,在BCL中也可以创建更多的类。

辅助类通常是静态类,因此不需要实例化它们。实例化托管.NET对象(尤其是帮助器类)没有太大的成本,这只是一个方便的问题

将静态类与最少的helper方法组合在一起并完成工作是非常诱人的。它们在代码中有自己的位置,特别是当存在确定性输入/输出时,可以使用它们。e、 g.计算字符串的哈希值,查找数字的平均值等

但是,不鼓励使用静态类的一个原因是,它们通常会干扰单元测试并出现各种问题。(赝品、鼹鼠、私人访问者等)

一种基于接口的方法甚至可以用于helper类,有助于对整个代码进行单元测试。对于涉及工作流的大型项目尤其如此,因为静态助手方法只是工作流的一部分

e、 假设您需要检查当前年份是否为闰年。编写一个快速的静态方法很有诱惑力

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(),您可能会遇到麻烦,因为返回值是不确定的。。i、 e.取决于当年,不建议单元测试随着时间的推移表现出不可预测/恶化

// 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);
}
公共班级生日
{
私人助理(日期助理);;
//任何调用方都可以注入自己版本的dateHelper。
公众生日(IDateHelper dateHelper)
{
这个。_dateHelper=dateHelper;
}
public int GetLeapYearDaysData()
{
//一些自我逻辑。。
//现在调用注入助手的方法。
var isLeapYear=this.\u dateHelper.isLeapYear();
//基于此值,可以返回100或200。
如果(每年)
{
返回100;
}
返回200;
}
}
//现在看看单元测试如何变得更加健壮和可靠
//这个单元测试更健壮
[测试]
public void TestGetLeapYearDaysData()
{
预期风险值=100;
//使用任何模拟框架或存根类
//可靠地告诉单元测试需要返回100。
var mockDateHelper=new Mock();
//使模拟助手返回true以进行闰年检查。
//我们不再受当前日期和时间的支配。
mockDateHelper.Setup(m=>m.IsLeapYear())。返回(true);
//将此模拟DateHelper注入到我们的生日课中
//我们肯定知道将返回的值。
var actual=新生日(mockDateHelper).GetLeapYearDaysData();
断言.AreEqual(预期、实际);
}
正如您所看到的,当helper方法基于接口时,它们就很容易测试。在一个大型项目的过程中,许多此类较小的静态方法最终会导致测试关键功能流的瓶颈

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

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

对。将
static
添加到类中表示它只包含静态成员,并且您永远无法实例化它。如果没有它,类的用户可能会感到困惑,并试图创建类的实例或变量。使用
静态
,这是不可能的

看来这正是你的情况

将静态修饰符添加到类中会对性能产生影响吗

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


但即使有差异,也很小。不要过早地进行优化,尤其是在微优化方面。

这一切都从什么时候我应该有一个静态方法开始,也就是说,当你对实例变量没有任何依赖时

现在,如果您的方法都不依赖于实例变量,那么您可以将类设置为静态


静态类服务于多个甚至更多的环境。

这在.NET以外的环境中有所不同吗?@ina您所说的其他环境是什么?我想不出支持静态类的其他主要语言(当然不是C++或java)。静态类如何干扰单元测试?