Skip to content

[CSharp] ClientProvider.BuildApiVersionFields produces duplicate field names for multi-service clients #10055

@ArcturusZhang

Description

@ArcturusZhang

Bug Report

Description

When a multi-service client is created via @client({ service: [ServiceA, ServiceB] }), ClientProvider.BuildApiVersionFields() can produce duplicate field names, causing BuildMethods() to crash with:

An item with the same key has already been added. Key: serviceTestsApiVersion
   at Dictionary2.Add(TKey key, TValue value)
   at Enumerable.ToDictionary(...)
   at ClientProvider.BuildMethods()

Impact

This crashes the Azure management plane generator (@azure-typespec/http-client-csharp-mgmt) when generating multi-service SDKs such as Azure Compute (which combines 4 services via @client({ service: [Compute, ComputeDisk, ComputeGallery, ComputeSku] })).

The mgmt generator does not use the ClientProvider or its API version fields at all — it replaces them with its own ARM resource-based client hierarchy. However, the crash occurs during ClientProvider.BuildMethods() before the mgmt generator gets a chance to override it, so the mgmt generator cannot work around this without patching the base ClientProvider.

Root Cause

In ClientOptionsProvider.BuildVersionProperties(), the property name for each service version is computed via:

string name = _inputClient.IsMultiServiceClient 
    ? ClientHelper.BuildNameForService(inputEnumType.Namespace, "Service", "ApiVersion") 
    : "Version";

When two services have namespaces that reduce to the same identifier (e.g. both share a common namespace segment from the C# output namespace), BuildNameForService produces the same property name for both services.

Then in ClientProvider.BuildApiVersionFields(), when VersionProperties.Count > 1:

string name = (count > 1) ? ("_" + propertyProvider.Name.ToVariableName()) : text;

Both services get the same field name (e.g. _serviceTestsApiVersion), and Fields.ToDictionary() in BuildMethods() crashes on the duplicate key.

Reproduction

Two ARM services combined via @client, with the output C# namespace causing name collisions:

ServiceOne/main.tsp:

@armProviderNamespace
@service(#{ title: "ServiceOne" })
@versioned(ServiceOne.Versions)
namespace ServiceOne;

enum Versions { v2024_01_01: "2024-01-01" }
// ... resource definition ...

ServiceTwo/main.tsp:

@armProviderNamespace
@service(#{ title: "ServiceTwo" })
@versioned(ServiceTwo.Versions)
namespace ServiceTwo;

enum Versions { v2024_06_01: "2024-06-01" }
// ... resource definition ...

client.tsp:

@client({ name: "MultiServiceClient", service: [ServiceOne, ServiceTwo] })
namespace MultiServiceCombine;

tspconfig.yaml with namespace Azure.Generator.MgmtTypeSpec.MultiService.Tests

Expected Behavior

Each service should get a unique field name for its API version field, even when namespace segments collide.

Actual Behavior

Both services produce the same field name serviceTestsApiVersion, causing ToDictionary to crash.

Metadata

Metadata

Labels

emitter:client:csharpIssue for the C# client emitter: @typespec/http-client-csharp

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions