我应该创建一个DateRange对象吗?

73

我的一些领域对象包含日期范围,其表示为一对起始日期和结束日期属性:

public class Period {
  public DateTime EffectiveDate { get; set; }
  public DateTime ThroughDate { get; set; }
}

public class Timeline {
  public DateTime StartDate { get; set; }
  public DateTime EndDate { get; set; }
}

我发现自己有很多这样的东西:

abstract public int Foo(DateTime startDate, DateTime endDate);
abstract public decimal Bar(DateTime startDate, DateTime endDate);
abstract public ICollection<C5.Rec<DateTime, DateTime>> FooBar(DateTime startDate, DateTime endDate);

最后一个让我想到了... 我应该实现一个DateRange类吗?我不知道BCL中是否有这样的类。

以我的经验来看,使对象层次更深往往会使事情变得更加复杂。这些对象确实会被发送到由ReportViewer控件显示的RDLC报表中,但这只是次要的。我会将视图弯曲到模型而不是相反。虽然我们没有绑定属性名称,但愿意妥协一些东西,比如:

public class DateRange {
  public DateTime StartDate { get; set; }
  public DateTime EndDate { get; set; }
}

Period p = new Period();
DateTime t = p.EffectiveDateRange.StartDate;

一个DateRange类的好处是可以集中验证结束日期是否在开始日期之后,这将简化我的方法签名:

abstract public int Foo(DateRange dateRange);
abstract public decimal Bar(DateRange dateRange);
abstract public ICollection<DateRange> FooBar(DateRange dateRange);

我不确定一个DateRange类是否会给我带来比它的价值更多的麻烦。你有什么意见吗?

另一个问题:我是否错过了BCL中的通用元组类?我知道有一些非常特定的元组类在各种命名空间中浮动。用C5类型污染我的公共领域方法签名感觉非常肮脏。


我认为一个日期范围类可以帮助解决问题。我一段时间前开始编写了这个类的基础框架:http://www.adamjamesnaylor.com/2012/11/04/C-DateRange-Class.aspx - Adam Naylor
@AdamNaylor:你的链接好像失效了... - testing
5个回答

52
不,你没有错过一个通用类。我在MiscUtil中有一个Range类型,你可能会感兴趣 - 它确实可以简化DateTime操作。参考Marc的答案,我记不清这是一个结构体还是一个类 - 当然你可以更改它。由于Marc的泛型把戏(至少假设你正在使用.NET 3.5 - 这在2.0中是可行的但目前不支持),它很容易进行步进。
Range<DateTime> range = 19.June(1976).To(DateTime.Today);

foreach (DateTime date in range.Step(1.Days())
{
    // I was alive in this day
}

(这也使用了一堆扩展方法 - 对测试比生产更有用。)

针对Marc答案中的另一个问题,Noda Time肯定能够更适当地表达日期概念,但我们目前还没有类似于范围的东西... 不过这是个好主意 - 我已经添加了一个功能请求


我已将我的采纳答案改为你的。在阅读了Range<T>代码后,我决定使用DateTime参数化版本作为我的DateRange领域类的核心。它让我达到了80%的效果。感谢你的贡献。 - quentin-starin
@JonSkeet 很希望能在 NuGet 上看到您的扩展! - Shimmy Weitzhandler
1
@Shimmy:现在这些都是Noda Time的扩展了...但是MiscUtil作为预发布版本已经在NuGet上了:https://www.nuget.org/packages/JonSkeet.MiscUtil/ - Jon Skeet
尝试使用 range.Step(x => x.AddDays(1)) 替代 range.Step(1.Days()),以便更好地编程。 - Demodave
看起来Noda Time现在确实有一个DateInterval类:https://nodatime.org/2.0.x/api/NodaTime.DateInterval.html - Zar Shardan
显示剩余2条评论

18

在.NET 4.0或更高版本中,新增了Tuple<>类型以处理多个值。

使用元组类型,您可以动态定义自己的值组合。您遇到的问题非常普遍,类似于函数想要返回多个值时的情况。以前,您必须使用out变量或创建一个新类来作为函数响应。

Tuple<DateTime, DateTime> dateRange =
    new Tuple<DateTime, DateTime>(DateTime.Today, DateTime.Now);

无论你选择哪条路线,我认为你肯定采取了正确的方法。你为成对出现的两个日期赋予了真正的含义。这就是自证代码,在代码结构中以最佳方式呈现。


自那时以来所做的更改意味着您甚至可以进一步简化语法 - (DateTime, DateTime) dateRange = (DateTime.Today, DateTime.Now); - Braydie

5
如果你需要大量处理日期,那么一段时间范围会很有用。这实际上是极为罕见的情况之一,你可能应该将其编写为不可变的struct结构。不过请注意,“Noda Time”在完成时可能会提供更多功能。我以前做过排班软件;我有几个这样的struct结构(针对稍微不同的工作)。
请注意,BCL中没有方便的构造函数来处理这个问题。
此外,当你有一个时间范围时,考虑到你可以集中所有美妙的方法(和可能的操作符):“包含”(datetime的?另一个范围的?包含/排除限制?),“相交”,通过偏移(持续时间)。非常适合拥有处理它的类型。请注意,在ORM级别上,如果ORM支持复合值,则更容易处理 - 我相信NHibernate支持,可能还有EF 4.0。

考虑到这是重构以更好地处理日期和范围操作,我认为给它一个类确实是有意义的。我只希望在这个项目中使用NHibernate。所有那些失去的时间写了一个相当不错的DAL,如果我这么说的话,但与之相比,还是相形见绌。不过下一个项目,我正在玩弄它,以便掌握它的要领。 - quentin-starin

2
我不知道是否有任何原生的.NET类具有DateRange的特性。最接近的可能是DateTime+TimeSpan或DateTime/DateTime组合。
我认为你想要的东西非常合理。

1
正如Mark和Jon所提到的,我会将其创建为值类型,这是不可变的。我会选择将其实现为结构体,并实现IEquatable和IComparable接口。
当使用像NHibernate这样的ORM时,您将能够将该值类型存储在表示实体的表中。

那么使用 NHibernate(Fluent)将日期范围作为子对象,并保持带有开始和结束日期列的扁平表结构不应该很难,对吧?我认为不会,但提前知道也是件好事。 - quentin-starin
你可以将DateRange实现为值对象,并在NHibernate中使用它作为“组件”。 然后,您确实可以保持具有开始和结束日期列的平面表结构。 - Frederik Gheysels

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