简体   繁体   English

两个日期作为C#中的属性

[英]Two dates as properties in C#

I want to have two simple properties: the start date and the end date. 我想要两个简单的属性:开始日期和结束日期。 I want to put a constraint that the start date must be before the end date. 我想设置一个约束条件,即开始日期必须在结束日期之前。 The problem arises when modifying both values - they may (together!) make a new, correct pair, but at the moment of adding them, there is an error. 修改两个值时会出现问题 - 它们可能(一起!)组成一个新的正确对,但在添加它们时,会出现错误。 Simple example: 简单的例子:

start = 5;
end = 10;

new_start = 20;
new_end = 30;

start = new_start; // error!
end = new_end;

That is why I introduced the third property. 这就是我介绍第三个属性的原因。 The thing is - the code looks terrible (especially the .Item1, .Item2 thing). 问题是 - 代码看起来很糟糕(尤其是.Item1,.Item2)。

Is there a way to do it in a better way in C#? 有没有办法在C#中以更好的方式做到这一点?

    private DateTime start;
    private DateTime end;

    public DateTime Start { get { return start; } }
    public DateTime End { get { return end; } }


    public Tuple<DateTime, DateTime> Dates
    {
        get
        {
            return new Tuple<DateTime, DateTime>(Start, End);
        }
        set
        {
            if (value.Item1 <= value.Item2)
            {
                start = value.Item1;
                end = value.Item2;
            }
            else
                throw new InvalidDates();
        }
    }

Well, you might think about whether it would make sense to have a type which represents a start/end combination. 好吧,你可能会考虑一个代表开始/结束组合的类型是否有意义。 A sort of... Interval . 一种... Interval (Yes, this is a not-so-subtle plug for Noda Time , which makes date/time handling generally better anyway. But you could create your own Interval struct for DateTime if you wanted...) Then you can have a single property to combine the start/end times, in a way which is guaranteed to be valid. (是的,这是一个不那么微妙的插头野田的时间 ,这使日期/时间一般都比较好处理无妨。但是你可以创建自己的Interval结构的DateTime ,如果你想...),那么你可以有一个单一的财产以确保有效的方式组合开始/结束时间。

But if you really want to keep them as separate properties, I'd go with a simple setter. 但如果你真的想把它们作为单独的属性,我会选择一个简单的setter。 I wouldn't create a separate exception for this though - just use ArgumentException : 我不会为此创建一个单独的异常 - 只需使用ArgumentException

public void SetDates(DateTime start, DateTime end)
{
    if (end < start)
    {
        throw new ArgumentException("End must be at or after start", "end");
    }
    this.start = start;
    this.end = end;
}

Wow. 哇。 Lots of answers. 很多答案。 Well, here's my take. 好吧,这是我的看法。

Create two public properties for the start and end dates, and then add a SetStartAndEndDates method that does the validation. 为开始日期和结束日期创建两个公共属性,然后添加执行验证的SetStartAndEndDates方法。 The public properties should have private setters. 公共财产应该有私人制定者。 Since the SetStartAndEndDates method throws an error if invalid dates are set, you'll want to create methods allowing you to test potential dates. 由于如果设置了无效日期, SetStartAndEndDates方法将引发错误,您将需要创建允许您测试潜在日期的方法。 To illustrate this methodology, I'll create a fictional CalendarEvent class: 为了说明这种方法,我将创建一个虚构的CalendarEvent类:

public class CalendarEvent
{
    public DateTime StartDate { get; private set; }
    public DateTime EndDate { get; private set; }

    public SetStartAndEndDates(DateTime start, DateTime end)
    {
        if (start <= end)
        {
            StartDate = start;
            EndDate = end;
        }
        else
        {
            throw new InvalidDates();
        }
    }

    public bool IsValidEndDate(DateTime end)
    {
        return StartDate <= end;
    }

    public bool IsValidStartDate(DateTime start)
    {
        return start <= EndDate;
    }

    public bool IsValidStartAndEndDate(DateTime start, DateTime end)
    {
        return start <= end;
    }
}

And to use it without throwing exceptions: 并使用它而不抛出异常:

var event = new Event();
var start = DateTime.Now;
var end = start.AddDays(7);

if (event.IsValidStartAndEndDate(start, end))
{
    event.SetStartAndEndDates(start, end);
}

Use a method to set them together: 使用方法将它们组合在一起:

public void SetDates(DateTime start, DateTime end)
{
    if(start >= end)
        throw new ArgumentException("start must be before end");
    this.start = start;
    this.end = end;
}

Use a method as a setter: 使用方法作为setter:

public void SetDates(DateTime startDate, EndDate endDate)
{
    if (startDate <= endDate)
    {
        start = startDate;
        end = endDate;
    }
    else
    {
        throw new InvalidDates();
    }
}

Instead of having two underlying DateTime , I would write a class that contains one DateTime for the start and one TimeSpan for the difference between the start and end. 我没有使用两个基础DateTime ,而是编写一个类,其中包含一个DateTime用于开始,一个TimeSpan用于开始和结束之间的差异。 The setter for the start would only change the DateTime and the setter for the end would only change the TimeSpan (giving an exception if it would make the TimeSpan negative). 开始的setter只会改变DateTime ,而end的setter只会改变TimeSpan (如果它会使TimeSpan负,则给出例外)。

You see behaviour like this in Google calendar and Outlook's calendar already, as I recall. 我记得,你已经在Google日历和Outlook的日历中看到了这样的行为。 There changing the start time of an event changes the end time too, but keeps the duration constant. 更改事件的开始时间也会更改结束时间,但会保持持续时间不变。

public class TimeWindow
{
    private TimeSpan duration;

    public DateTime StartTime { get; set; }

    public DateTime EndTime
    {
        get
        {
            return this.StartTime.Add(this.duration);
        }

        set
        {
            // this will throw a ArgumentOutOfRangeException if value is smaller than StartTime
            this.duration = value.Subtract(this.StartTime);
        }
    }

    public void SetStartAndEnd(DateTime start, DateTime end)
    {
        this.StartTime = start;
        this.EndTime = end;
    }
}

OO encapsulation isn't always about pretty implementation, it's often about pretty interfaces that provide consistent "black box" behavior. OO封装并不总是关于漂亮的实现,它通常是关于提供一致的“黑盒子”行为的漂亮接口。 If writing the code that "looks terrible" provides a smooth interface with behavior consistent with the design, then what's the big deal? 如果编写“看起来很糟糕”的代码提供了与设计一致的行为的流畅界面,那么最重要的是什么? I think the solution you have is perfectly valid to keep the internals of the class consistent. 我认为你所拥有的解决方案完全有效,可以保持班级内部的一致性。

Take the validation checks out of the setters and add a validate method. 从设置器中取出验证检查并添加验证方法。 You can then set them in any order without exception and check the final outcome once you think they are ready by calling validate. 然后,您可以毫无例外地以任何顺序设置它们,并在您认为通过调用validate之后检查最终结果。

private DateTime start;
private DateTime end;

public DateTime Start { get { return start; } }
public DateTime End { get { return end; } }

public void Validate()
{
    if (end.Ticks < start.Ticks)
        throw new InvalidDates();
}

Another way which is not listed in answers is to implement ISupportInitialize interface. 答案中未列出的另一种方法是实现ISupportInitialize接口。 Before modifying dates you call BeginInit method and after - EndInit . 在修改日期之前,您调用BeginInit方法,然后调用 - EndInit On EndInit validate dates. EndInit验证日期。

public class SomeClass : ISupportInitialize
{
    private bool initializing;
    private DateTime start;
    private DateTime end;

    public DateTime Start
    {
        get { return start; }
        set { CheckInitializing(); start = value; }
    }

    public DateTime End
    {
        get { return end; }
        set { CheckInitializing(); end = value; }
    }

    private void CheckInitializing()
    {
        if (!initializing)
        { throw new InvalidOperationException("Can not execute this operation outside a BeginInit/EndInit block."); }
    }

    public void BeginInit()
    {
        if (initializing)
        { throw new InvalidOperationException("Can not have nested BeginInit calls on the same instance."); }

        initializing = true;
    }

    public void EndInit()
    {
        if (!initializing)
        { throw new InvalidOperationException("Can not call EndInit without a matching BeginInit call."); }

        if (start <= end)
        { throw new InvalidDates(); }

        initializing = false;
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM