Protobuf: Current head C# code throws ArgumentNullException when retrieving descriptors with extensions

Created on 30 Oct 2019  路  9Comments  路  Source: protocolbuffers/protobuf

What version of protobuf and what language are you using?
Version: master
Language: C#

What operating system (Linux, Windows, ...) and version?

Windows 10.

What runtime / compiler are you using (e.g., python version or gcc version)

protoc from 3.10.1 on Windows

What did you do?

Proto file test.proto:

syntax = "proto3";
import "extensions.proto";
message TestMessage {}

Proto file extensions.proto:

syntax = "proto3";
import "google/protobuf/descriptor.proto";
extend google.protobuf.MethodOptions {
  string method_ext = 1234567;
}

Generate code:

protoc.exe -I. -Ipath/to/tools test.proto extensions.proto --csharp_out=.

C# code:

using System;

class Program
{
    static void Main(string[] args)
    {
        var descriptor = TestMessage.Descriptor;
        Console.WriteLine(descriptor);
    }
}

What did you expect to see

The descriptor.

What did you see instead?

The following exception - full paths removed for simplicity.

Unhandled exception. System.TypeInitializationException: The type initializer for 'TestReflection' threw an exception.
 ---> System.ArgumentNullException: Value cannot be null. (Parameter 'extension')
   at Google.Protobuf.ProtoPreconditions.CheckNotNull[T](T value, String name) in ProtoPreconditions.cs:line 57
   at Google.Protobuf.ExtensionRegistry.Add(Extension extension) in ExtensionRegistry.cs:line 80
   at Google.Protobuf.ExtensionRegistry.AddRange(IEnumerable`1 extensions) in ExtensionRegistry.cs:line 94
   at Google.Protobuf.Reflection.FileDescriptor.AddAllExtensions(FileDescriptor[] dependencies, GeneratedClrTypeInfo generatedInfo, ExtensionRegistry registry) in FileDescriptor.cs:line 447
   at Google.Protobuf.Reflection.FileDescriptor.FromGeneratedCode(Byte[] descriptorData, FileDescriptor[] dependencies, GeneratedClrTypeInfo generatedCodeInfo) in FileDescriptor.cs:line 422
   at TestReflection..cctor() in Test.cs:line 27
   --- End of inner exception stack trace ---
   at TestReflection.get_Descriptor() in Test.cs:line 18
   at TestMessage.get_Descriptor() in Test.cs:line 45
   at Program.Main(String[] args) in Program.cs:line 7
bug c#

Most helpful comment

@alexsandro-xpt: This isn't a gRPC restriction, it's a protobuf restriction - and a natural one that comes from protobuf treating an unset string field as an empty string.

If you need to tell the difference between null and empty, use the StringValue message. But if you're happy enough using empty strings, you don't need long-winded code - just use the null coalescing ?? operator:

csharp
return new UserReplay
{
    Name = bridge.UserAccount.Data?.Name ?? "",
    Email = bridge.UserAccount.Data?.Email ?? "",
    Phone = bridge.UserAccount.Data?.Phone ?? ""
};

Again, all of this is entirely separate from the topic of this issue, which is to do with fetching descriptors. I suggest if you have further questions about null handling in protobuf, you ask on Stack Overflow.

All 9 comments

Okay, the problem is that ExtensionsReflection.Descriptor.Extensions.UnorderedExtensions[0] is a FieldDescriptor (method_ext) but it has a null Extension property. Looking into why that is.

I've regenerated with the HEAD generator, and the problem goes away. So this is mostly a case of working out what to do with old generated code. We should probably just ignore extensions with an Extension property of null.

@jskeet I have the same problem, how to fix this?

I'm using dotnet core 3.0

[16:46:34 ERR] Error when executing service method 'GetUser'. System.ArgumentNullException: Value cannot be null. (Parameter 'value') at Google.Protobuf.ProtoPreconditions.CheckNotNull[T](T value, String name) at MultiNetworkServices.WebApi.Services.UserReplay.set_Phone(String value) in /app/MultiNetworkServices.WebApi/obj/Release/netcoreapp3.0/UserInformation.cs:line 243 at MultiNetworkServices.WebApi.Services.UserInformationService.GetUser(UserRequest request, ServerCallContext context) in /app/MultiNetworkServices.WebApi/Services/UserInformationService.cs:line 32 at Grpc.AspNetCore.Server.Internal.CallHandlers.UnaryServerCallHandler`3.HandleCallAsyncCore(HttpContext httpContext, HttpContextServerCallContext serverCallContext) at Grpc.AspNetCore.Server.Internal.CallHandlers.UnaryServerCallHandler`3.HandleCallAsyncCore(HttpContext httpContext, HttpContextServerCallContext serverCallContext) at Grpc.AspNetCore.Server.Internal.CallHandlers.ServerCallHandlerBase`3.<HandleCallAsync>g__AwaitHandleCall|17_0(HttpContextServerCallContext serverCallContext, Method`2 method, Task handleCall) [16:46:34 INF] Executed endpoint 'gRPC - /User.UserInformation/GetUser' [16:46:34 INF] HTTP POST /User.UserInformation/GetUser responded 200 in 21.0825 ms [16:46:34 INF] Request finished in 21.392400000000002ms 200 application/grpc

@AlanBurlison: I don't believe that's the same problem. It looks like you're trying to set the Phone property to a null value when it doesn't support that.

@jskeet it's null from database, the .proto file have a:
proto message UserReplay { string name = 1; string email = 2; string phone = 3; }

@alexsandro-xpt: Right, that's an app issue then. You need to decide what you want to do with a null value, given that it can't be represented directly in your proto. That's not at all what this issue is about, I'm afraid.

@jskeet Are you told me the C# gRPC can't work with null properts message and then I should decide like that?

````csharp
var userReplay = new UserReplay();

if (!string.IsNullOrWhiteSpace(bridge.UserAccount.Data?.Name))
{
userReplay.Name = bridge.UserAccount.Data.Name;
}

if (!string.IsNullOrWhiteSpace(bridge.UserAccount.Data?.Email))
{
userReplay.Email = bridge.UserAccount.Data.Email;
}

if (!string.IsNullOrWhiteSpace(bridge.UserAccount.Data?.Phone))
{
userReplay.Phone = bridge.UserAccount.Data.Phone;
}

return userReplay;
````

I can't do that?
csharp return new UserReplay { Name = bridge.UserAccount.Data?.Name, Email = bridge.UserAccount.Data?.Email, Phone = bridge.UserAccount.Data?.Phone };

@ObsidianMinor

@alexsandro-xpt: This isn't a gRPC restriction, it's a protobuf restriction - and a natural one that comes from protobuf treating an unset string field as an empty string.

If you need to tell the difference between null and empty, use the StringValue message. But if you're happy enough using empty strings, you don't need long-winded code - just use the null coalescing ?? operator:

csharp
return new UserReplay
{
    Name = bridge.UserAccount.Data?.Name ?? "",
    Email = bridge.UserAccount.Data?.Email ?? "",
    Phone = bridge.UserAccount.Data?.Phone ?? ""
};

Again, all of this is entirely separate from the topic of this issue, which is to do with fetching descriptors. I suggest if you have further questions about null handling in protobuf, you ask on Stack Overflow.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

tbillington picture tbillington  路  3Comments

TimmKayserHere picture TimmKayserHere  路  3Comments

paquettg picture paquettg  路  3Comments

orian picture orian  路  3Comments

MikeSilvis picture MikeSilvis  路  3Comments