diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/OutputTypes/CSharpType.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/OutputTypes/CSharpType.cs index 9d251e6925..bab9c08fcb 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/OutputTypes/CSharpType.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/OutputTypes/CSharpType.cs @@ -66,8 +66,8 @@ public class CSharpType /// Optional flag to determine if the constructed type should be nullable. Defaults to false. public CSharpType(Type type, bool isNullable = false) : this( type, - isNullable, - type.IsGenericType ? type.GetGenericArguments().Select(p => new CSharpType(p)).ToArray() : Array.Empty()) + type.IsGenericType ? type.GetGenericArguments().Select(p => new CSharpType(p)).ToArray() : Array.Empty(), + isNullable) { } /// @@ -97,6 +97,16 @@ public CSharpType(Type type, IReadOnlyList arguments, bool isNullabl { Debug.Assert(type.Namespace != null, "type.Namespace != null"); + // handle nullable value types explicitly because they are implemented using generic type `System.Nullable` + var underlyingValueType = Nullable.GetUnderlyingType(type); + if (underlyingValueType != null) + { + // in this block, we are converting input like `typeof(int?)` into the way as if they input: `typeof(int), isNullable: true` + type = underlyingValueType; + arguments = type.IsGenericType ? type.GetGenericArguments().Select(p => new CSharpType(p)).ToArray() : Array.Empty(); + isNullable = true; + } + _type = type.IsGenericType ? type.GetGenericTypeDefinition() : type; ValidateArguments(_type, arguments); diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/CSharpTypeTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/CSharpTypeTests.cs index 67d715c3bf..e5cf8aed3e 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/CSharpTypeTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/CSharpTypeTests.cs @@ -1,14 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using System.Collections.Generic; using System; -using NUnit.Framework; -using System.Linq; +using System.Collections.Generic; using System.Collections.Immutable; -using Moq; using System.IO; +using System.Linq; using System.Text; +using Moq; +using NUnit.Framework; namespace Microsoft.Generator.CSharp.Tests { @@ -394,5 +394,71 @@ public void TestToString(Type type, string expectedString) Assert.AreEqual(expected, actual); } + + [TestCaseSource(nameof(ValidateNullableTypesData))] + public void ValidateNullableTypes(Type type, IReadOnlyList expectedArguments, bool expectedIsNullable) + { + var csharpType = new CSharpType(type); + + CollectionAssert.AreEqual(expectedArguments, csharpType.Arguments); + Assert.AreEqual(expectedIsNullable, csharpType.IsNullable); + } + + private static object[] ValidateNullableTypesData = [ + new object[] + { + typeof(int), Array.Empty(), false + }, + new object[] + { + typeof(int?), Array.Empty(), true + }, + new object[] + { + typeof(Uri), Array.Empty(), false + }, + new object[] + { + typeof(Guid), Array.Empty(), false + }, + new object[] + { + typeof(Guid?), Array.Empty(), true + }, + new object[] + { + typeof(TestStruct), new CSharpType[] { typeof(int) }, false + }, + new object[] + { + typeof(TestStruct?), new CSharpType[] { typeof(int) }, true + }, + new object[] + { + typeof(TestStruct), new CSharpType[] { typeof(int?) }, false + }, + new object[] + { + typeof(TestStruct?), new CSharpType[] { typeof(int?) }, true + }, + new object[] + { + typeof(TestStruct>), new CSharpType[] { typeof(TestStruct) }, false + }, + new object[] + { + typeof(TestStruct>?), new CSharpType[] { typeof(TestStruct) }, true + }, + new object[] + { + typeof(TestStruct?>), new CSharpType[] { typeof(TestStruct?) }, false + }, + new object[] + { + typeof(TestStruct?>?), new CSharpType[] { typeof(TestStruct?) }, true + }, + ]; + + internal struct TestStruct { } } }