-
Notifications
You must be signed in to change notification settings - Fork 15.6k
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
C#: Mark GetOption API as obsolete and expose the "GetOptions()" method on descriptors instead #7434
C#: Mark GetOption API as obsolete and expose the "GetOptions()" method on descriptors instead #7434
Conversation
Alternative approaches are in PRs https://github.com/protocolbuffers/protobuf/pull/7130/files |
I'm fine with deprecating the existing API, but we shouldn't make breaking changes by removing the existing one unless you're planning on creating a major version bump (which I'd strongly discourage). I'm uncomfortable with the idea of APIs being labelled as "experimental" in general - particularly when that designation is only visible if you happen to read the right documentation. I'm similarly nervous about my own #7429 PR, but at least that's in the generated code rather than the support library. (You only get broken when you regenerate, and then it's at compile time.) This change could easily mean that a minor version bump in which support library is consumed ends up breaking a deployed application. |
I'm fine with both approaches (removing the GetOption method or only marking it as deprecated).
|
If the option had only been available for Proto2-based code, I'd be at least more comfortable - but we've been using it (in API generation) for proto3-based options. Again, I don't think marking something as experimental just in release notes is a good way of excusing breaking changes. If we want to be able to make breaking changes in the support library itself, I really think we need a better way of doing so. (Marking it as deprecated would, somewhat oddly, be a reasonable starting point - something that brings it to the attention of users.) And yes, it needed fixing to start with - but it was fixed. It's not great in terms of API surface, but it works for at least some scenarios (e.g. how we're using it in https://github.com/googleapis/gapic-generator-csharp) I tend to think that the benefits of violating semver have to be really, really huge to counter the downsides, which can include lack of trust in future releases. Sometimes it's the best-of-a-bad-bunch option, but in this case I think using deprecation is better. |
Fair enough, I'll add back the GetOption methods and mark them as obsolete. |
Fab, thanks. I can then have a look at the "new" API without being distracted ;) |
Done. |
Note that custom options can be self-referential. Here is valid .proto file that we should ensure works properly in C#: syntax = "proto2";
import "google/protobuf/descriptor.proto";
message FooOptions {
// Custom field option used in definition of the custom option's message.
optional int32 foo = 1 [(foo_options) = {foo: 1234}];
}
extend google.protobuf.FieldOptions {
// Custom field option used on the definition of that field option.
optional int32 bar_options = 1000 [(bar_options) = 1234];
optional FooOptions foo_options = 1001;
} |
I'm still unsure how I feel about doing defensive copies but that may just be the Rust developer in me that's worried and not the C# developer. It will make the API simpler in the long run and will be easier to maintain so it should be ok. |
Yes, I'm pretty sure we need the defensive copy. It would be awful if reflection could modify the descriptors globally. |
/// Custom options can be retrieved as extensions of the returned message. | ||
/// NOTE: A defensive copy is created each time this property is retrieved. | ||
/// </summary> | ||
public EnumOptions Options => (Proto.Options as IDeepCloneable<EnumOptions>)?.Clone(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can Proto.Options
on an EnumDescriptor
ever not be IDeepCloneable<EnumOptions>
? Same question for all the other as
casts. I prefer to do a regular cast unless there is a reason.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, removed the cast.
public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber); | ||
|
||
/// <summary> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can null ever be returned?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improved the docs.
Yes I agree that it would be better to avoid the copy if possible. But without |
By the way, here's an even more diabolical example of custom options. syntax = "proto2";
import "google/protobuf/descriptor.proto";
message FooOptions {
// Custom field option used in definition of the extension message.
optional int32 int_opt = 1 [(foo_options) = {
int_opt: 1
[foo_int_opt]: 2
[foo_foo_opt]: {
int_opt: 3
}
}];
extensions 1000 to max;
}
extend google.protobuf.FieldOptions {
// Custom field option used on the definition of that field option.
optional int32 bar_options = 1000 [(bar_options) = 1234];
optional FooOptions foo_options = 1001;
}
extend FooOptions {
optional int32 foo_int_opt = 1000;
optional FooOptions foo_foo_opt = 1001;
} |
looks like GetOption() is taken, GetOptions() is not. Not sure if Options property or GetOptions method is better (the clone is actually going to be pretty cheap for most normal cases). |
Do these proto definitions already exist somewhere in the protobuf codebase? I'd expect so because presumably other languages already have tests for these cases? |
Friendly ping, can we get this reviewed and merged? |
It wasn't clear to me that it was ready for another review given the nature of the last few comments. I'll have another look tomorrow. |
I think the only undecided topic is whether to expose |
public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber); | ||
|
||
/// <summary> | ||
/// The <c>EnumOptions</c>, defined in <c>descriptor.proto</c>. | ||
/// If the options message is not present (=there are no options), <c>null</c> is returned. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// If the options message is not present (=there are no options), <c>null</c> is returned. | |
/// If the options message is not present (i.e. there are no options), <c>null</c> is returned. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Ditto in all options, of course.)
If it is making a copy, putting it behind a method like |
@jskeet do you have a preference? I still think using a property is fine. |
I'd just about prefer a method here - it really does make it clearer - but I'm not massively bothered either way. |
Ok, looks like I've been outvoted then. I'll turn this into a GetOptions() method. |
@haberman all feedback addressed. |
Test failures seem unrelated, merging. |
@haberman I think we should cherry-pick this for 3.12.x branch (It's a fix). |
…se_options C#: Get rid of broken GetOption API and expose the "GetOptions()" method on descriptors instead
The currently existing new API for getting custom options (introduced in #5350 as part of proto2 extensions support) has some serious issues
This PR removes the unnecessary (and partially broken) GetOption API and replaces it by a more straightforward approach:
(the GetOption API has been marked as "experimental and subject to change" when releasing the C# proto2 support, so removing it if fine to do).