Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make Timestamp implement IComparable (fixes #4267) #4318

Merged

Conversation

warrenfalk
Copy link
Contributor

I've implemented IComparable<Timestamp> for the Timestamp class.

I have also implemented comparison operators <, >, <=, >=, ==, !=.

This fixes #4267 and is intended to make usage much more natural:

if (thing.Happened > setting.Deadline)
{
    // This is late
}

var inChronologicalOrder = listOfEvents.OrderBy(e => e.WhenItHappened);

@grpc-kokoro
Copy link

Thanks for your pull request. The automated tests will run as soon as one of the admins verifies this change is ok for us to run on our infrastructure.

1 similar comment
@grpc-kokoro
Copy link

Thanks for your pull request. The automated tests will run as soon as one of the admins verifies this change is ok for us to run on our infrastructure.

@anandolee anandolee self-requested a review March 19, 2018 20:28
@anandolee anandolee self-assigned this Mar 19, 2018
/// <returns>an integer indicating whether this timestamp precedes or follows the other</returns>
public int CompareTo(Timestamp other)
{
return other is null ? 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should consider that Timestamp may not normalized

@anandolee
Copy link
Contributor

Friendly ping @warrenfalk, are you still working on this PR?

@warrenfalk
Copy link
Contributor Author

Oh yeah, forgot about this. Yep give me a few minutes.

@warrenfalk warrenfalk force-pushed the timestamp-comparison-operators branch from 783ca37 to f50064e Compare June 25, 2018 23:03
@warrenfalk
Copy link
Contributor Author

Thanks for the ping. This has been updated to handle non-normalized Timestamps.

/// <returns>true if the two timestamps refer to the same nanosecond</returns>
public static bool operator ==(Timestamp a, Timestamp b)
{
return ReferenceEquals(a, b) || (a is null ? (b is null ? true : false) : a.Equals(b));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a.Equals(b): Maybe I missed somewhere but didn't see Equals() implementation in this file. Shouldn't use a.CompareTo(b)==0 instead? Maybe add a test to compare two equal timestamps which are not normalized

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. I think normalization at this level is a mistake and recommend reconsidering removing it. Let me answer your questions and explain my reasoning, and you can decide what you want to do.

Equals() is already implemented in the other, generated half of the partial class. I can't change the implementation there, and I don't think we want to because ultimately Timestamp is a protocol buffer structure and it should follow protocol buffer semantics where "equals" means "structurally all fields are equal".

So if a user wants "time equality" instead of just "protocol buffer" equality then the user must (already, today) ensure that her structures are all normalized.

Keep in mind that equality and comparison are semantically separate things, which is why IEquatable and IComparable are separate interfaces. "Comparison" deals with ordering/sorting. So CompareTo(a) == 0 does not mean the same thing as Equals(a) == true and they do not necessarily have to match.

Additionally the operators don't match each other either. The < > <= >= operators are not necessarily related to ==. So it cannot be asserted that !(a < b) && !(a > b) == (a == b).

But of course people expect them to match, so I think we should make them match.

If you want them to match, however, you have to give up on normalization because Equals ignores it and can't be changed. So if you want "time comparison" then you have to pre-normalize, just like you have to already pre-normalize if you want "time equality".

So the bottom line is you can either have comparisons that normalize, or comparisons that match equality, but not both. You have to choose.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, we should make them match. I talked with our tech lead, we think it make sense to not normalize for all comparison. Add comments that if the timestamp is not normalized the result may not correct in the API documentation

{
if (!IsNormalized(Seconds, Nanos))
return Normalize(Seconds, Nanos).CompareTo(other);
if (other == null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better put null check in the first place (before normalize this timestamp).
Why compare to null should return 1? Is any other well known compare do like this that consider null as the smallest?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The BCL treats null this way. Consider: Debug.Assert(1 == "bcl".CompareTo(null));

See my other comment on normalization and how I think we should reconsider not doing it, but if we keep it, here are my thoughts here:

Doing a null check first will do the null check twice. Since the only Normalize() function we have creates an entirely new Timestamp instance, we just call that one's CompareTo() which will be doing its own null check.

I would understand if you wanted to do it anyway, considering un-normalized to be the exceptional case, and normalization to be the more expensive operation. So let me know. But I recommend we just not normalize in the first place.

/// </summary>
/// <param name="other">Timestamp to compare</param>
/// <returns>an integer indicating whether this timestamp precedes or follows the other</returns>
public int CompareTo(Timestamp other)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does CompareTo needs to be public? I'd prefer to make it private if possible and only expose < == operations etc. to users

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, we are implementing IComparable, so does it have to be public? It does have to be exposed, yes. We could make the implementation explicit, but it will always be public on the interface and therefore always exposed to users and so there's no way around exposing it. Note that for reference, these are always public in the BCL, as far as I can tell.

@warrenfalk warrenfalk force-pushed the timestamp-comparison-operators branch 2 times, most recently from c905c3a to 8c0d098 Compare July 3, 2018 17:23
@warrenfalk
Copy link
Contributor Author

OK, added remarks to documentation about possible unexpected results when comparing non-normalized values, and removed normalization from comparison implementation.

Copy link
Contributor

@anandolee anandolee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just change the comments a little bit

/// Given another timestamp, returns 0 if the timestamps are equivalent, -1 if this timestamp precedes the other, and 1 otherwise
/// </summary>
/// <remarks>
/// The result of comparing non-normalized timestamps is not specified and may give unexpected results
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Make sure the timestamps are normalized. Comparing non-normalized timestamps is not specified and may give unexpected results."

@warrenfalk warrenfalk force-pushed the timestamp-comparison-operators branch from 8c0d098 to 611e68d Compare July 9, 2018 11:26
@anandolee anandolee merged commit 96833b8 into protocolbuffers:master Jul 9, 2018
@anandolee anandolee mentioned this pull request Sep 24, 2018
9 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[C#] WellKnownTypes.Timestamp does not implement IComparable
6 participants