我有一个数学辅助类,其中每个函数都是静态的,即作为参数输入的参数,返回值。我应该将整个类声明为静态吗?将静态修改器添加到类中是否会对性能产生影响?
另外,我不确定this guideline的含义是什么:"不要将静态类视为杂项桶。" - 我有几个类只是一堆杂项静态函数......
答案 0 :(得分:6)
制作像static
这样的课程非常好,事实上,如果你看System.Math
,你也会看到它static
:
public static class Math
指南试图说的是你不应该把你所拥有的每一个静态方法都放在一个静态类中,它会做所有事情,并为静态方法扮演一个桶的角色。相反,如果合适,请使用与相同功能相关的方法创建较小的 util 类,例如使用System.Math
完成,并在BCL内完成更多操作。
答案 1 :(得分:1)
我应该将整个类声明为静态吗?
是。向类添加static
表示它只包含静态成员,并且您无法实例化它。没有它,您的班级用户可能会感到困惑,并尝试创建您的班级的实例或变量。使用static
,这是不可能的。
看起来这正是你的情况。
将静态修改器添加到类中会对性能产生影响吗?
不,调用静态方法将始终具有相同的性能特征,包含类是否为static
无关紧要。实际上,静态类的整个概念在CIL级别不存在,它们只是密封的抽象类(不能用C#编译的组合)。
但即使存在差异,也会很小。不要过早优化,特别是在微观优化方面。
答案 2 :(得分:0)
Helper类通常是静态类,因此您不需要实例化它们。实例化托管.NET对象(特别是帮助程序类)没有太大的成本,只是为了方便。
使用最小的辅助方法组合一个静态类并完成工作是非常诱人的。它们在代码中占有一席之地,特别是在确定性输入/输出时可以使用它们。例如ComputeHash of a string,Find Average of numbers。
但其中一个原因是,静态类是不鼓励的,因为它们通常会干扰单元测试并出现各种问题。 (假货,鼹鼠,私人配件等)
基于接口的方法甚至是辅助类,有助于对整个代码进行单元测试。对于涉及工作流的大型项目尤其如此,因此静态辅助方法只是工作流的一部分。
e.g。假设您需要检查当前年份是否为闰年。编写一个快速静态方法很有诱惑力。
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);
}
正如您所看到的,辅助方法基于接口的那一刻,它们很容易测试。在一个大项目的过程中,许多这种较小的静态方法最终会导致测试关键功能流程的瓶颈。
因此,提前了解这一陷阱并提前进行额外投资是值得的。基本上确定哪些类/方法需要是静态的,哪些不应该是。
答案 3 :(得分:0)