Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 1 addition & 10 deletions src/libraries/System.Private.CoreLib/src/System/Nullable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,16 +104,7 @@ public static bool Equals<T>(T? n1, T? n2) where T : struct
{
ArgumentNullException.ThrowIfNull(nullableType);

if (nullableType.IsGenericType && !nullableType.IsGenericTypeDefinition)
{
// Instantiated generic type only
Type genericType = nullableType.GetGenericTypeDefinition();
if (ReferenceEquals(genericType, typeof(Nullable<>)))
{
return nullableType.GetGenericArguments()[0];
}
}
return null;
return nullableType.GetNullableUnderlyingType();
}

/// <summary>
Expand Down
21 changes: 21 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/Type.cs
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,27 @@ protected virtual TypeCode GetTypeCodeImpl()
public virtual bool IsInstanceOfType([NotNullWhen(true)] object? o) => o != null && IsAssignableFrom(o.GetType());
public virtual bool IsEquivalentTo([NotNullWhen(true)] Type? other) => this == other;

/// <summary>
/// Returns the underlying type argument of a <see cref="Nullable{T}"/> type.
/// </summary>
/// <returns>
/// The type argument of the <see cref="Nullable{T}"/> type if the current type represents
/// a closed generic <see cref="Nullable{T}"/>; otherwise, <see langword="null"/>.
/// </returns>
public virtual Type? GetNullableUnderlyingType()
{
if (IsGenericType && !IsGenericTypeDefinition)
{
Type genericType = GetGenericTypeDefinition();
if (ReferenceEquals(genericType, typeof(Nullable<>)))
{
return GetGenericArguments()[0];
}
}

return null;
}

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2085:UnrecognizedReflectionPattern",
Justification = "The single instance field on enum types is never trimmed")]
[Intrinsic]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,23 @@
private volatile RoType? _lazyUnderlyingEnumType;
public sealed override Array GetEnumValues() => throw new InvalidOperationException(SR.Arg_InvalidOperation_Reflection);

// Nullable methods
#if NET
public sealed override Type? GetNullableUnderlyingType()

Check failure on line 332 in src/libraries/System.Reflection.MetadataLoadContext/src/System/Reflection/TypeLoading/Types/RoType.cs

View check run for this annotation

Azure Pipelines / runtime-dev-innerloop (Build linux-x64 debug Libraries_WithPackages)

src/libraries/System.Reflection.MetadataLoadContext/src/System/Reflection/TypeLoading/Types/RoType.cs#L332

src/libraries/System.Reflection.MetadataLoadContext/src/System/Reflection/TypeLoading/Types/RoType.cs(332,38): error CS0115: (NETCORE_ENGINEERING_TELEMETRY=Build) 'RoType.GetNullableUnderlyingType()': no suitable method found to override

Check failure on line 332 in src/libraries/System.Reflection.MetadataLoadContext/src/System/Reflection/TypeLoading/Types/RoType.cs

View check run for this annotation

Azure Pipelines / runtime-dev-innerloop

src/libraries/System.Reflection.MetadataLoadContext/src/System/Reflection/TypeLoading/Types/RoType.cs#L332

src/libraries/System.Reflection.MetadataLoadContext/src/System/Reflection/TypeLoading/Types/RoType.cs(332,38): error CS0115: (NETCORE_ENGINEERING_TELEMETRY=Build) 'RoType.GetNullableUnderlyingType()': no suitable method found to override
{
if (IsConstructedGenericType)
{
RoType? nullableOfT = Loader.TryGetCoreType(CoreType.NullableT);
if (nullableOfT is not null && GetGenericTypeDefinition() == nullableOfT)
{
return GenericTypeArguments[0];
}
}

return null;
}
#endif

#if NET
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2085:UnrecognizedReflectionPattern",
Justification = "Enum Types are not trimmed.")]
Expand Down
1 change: 1 addition & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6604,6 +6604,7 @@ protected Type() { }
public virtual string? GetEnumName(object value) { throw null; }
public virtual string[] GetEnumNames() { throw null; }
public virtual System.Type GetEnumUnderlyingType() { throw null; }
public virtual System.Type? GetNullableUnderlyingType() { throw null; }
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("It might not be possible to create an array of the enum type at runtime. Use Enum.GetValues<T> or the GetEnumValuesAsUnderlyingType method instead.")]
public virtual System.Array GetEnumValues() { throw null; }
public virtual System.Array GetEnumValuesAsUnderlyingType() { throw null; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,5 +376,7 @@
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.Serialization.Formatters\src\System.Runtime.Serialization.Formatters.csproj"
Private="true"
SetTargetFramework="TargetFramework=$(NetCoreAppMinimum)" />
<!-- Used for MetadataLoadContext tests in NullableTests. -->
<ProjectReference Include="$(LibrariesProjectRoot)System.Reflection.MetadataLoadContext\src\System.Reflection.MetadataLoadContext.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Xunit;

namespace System.Tests
Expand Down Expand Up @@ -88,12 +91,78 @@ public static void GetUnderlyingType(Type nullableType, Type? expected)
Assert.Equal(expected, Nullable.GetUnderlyingType(nullableType));
}

[Theory]
[InlineData(typeof(int?), typeof(int))]
[InlineData(typeof(int), null)]
[InlineData(typeof(G<int>), null)]
public static void GetNullableUnderlyingType_RuntimeType(Type type, Type? expected)
{
Assert.Equal(expected, type.GetNullableUnderlyingType());
}

[Fact]
public static void GetUnderlyingType_NullType_ThrowsArgumentNullException()
{
AssertExtensions.Throws<ArgumentNullException>("nullableType", () => Nullable.GetUnderlyingType((Type)null));
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))]
public static void GetUnderlyingType_MetadataLoadContext_NullableInt_ReturnsUnderlyingType()
{
string[] runtimeAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll");
var resolver = new PathAssemblyResolver(runtimeAssemblies);
using var mlc = new MetadataLoadContext(resolver);

Assembly coreAssembly = mlc.LoadFromAssemblyName("System.Runtime");
Type intType = coreAssembly.GetType("System.Int32")!;
Type nullableIntType = coreAssembly.GetType("System.Nullable`1")!.MakeGenericType(intType);

// Test via Nullable.GetUnderlyingType (forwards to the virtual)
Type? underlying = Nullable.GetUnderlyingType(nullableIntType);
Assert.NotNull(underlying);
Assert.Equal("System.Int32", underlying.FullName);

// Test via Type.GetNullableUnderlyingType directly
Type? underlyingDirect = nullableIntType.GetNullableUnderlyingType();
Assert.NotNull(underlyingDirect);
Assert.Equal("System.Int32", underlyingDirect.FullName);
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))]
public static void GetUnderlyingType_MetadataLoadContext_NonNullableTypes_ReturnsNull()
{
string[] runtimeAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll");
var resolver = new PathAssemblyResolver(runtimeAssemblies);
using var mlc = new MetadataLoadContext(resolver);

Assembly coreAssembly = mlc.LoadFromAssemblyName("System.Runtime");
Type intType = coreAssembly.GetType("System.Int32")!;
Type stringType = coreAssembly.GetType("System.String")!;
Type kvpType = coreAssembly.GetType("System.Collections.Generic.KeyValuePair`2")!.MakeGenericType(intType, stringType);

Assert.Null(Nullable.GetUnderlyingType(intType));
Assert.Null(Nullable.GetUnderlyingType(stringType));
Assert.Null(Nullable.GetUnderlyingType(kvpType));

Assert.Null(intType.GetNullableUnderlyingType());
Assert.Null(stringType.GetNullableUnderlyingType());
Assert.Null(kvpType.GetNullableUnderlyingType());
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))]
public static void GetUnderlyingType_MetadataLoadContext_OpenNullable_ReturnsNull()
{
string[] runtimeAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll");
var resolver = new PathAssemblyResolver(runtimeAssemblies);
using var mlc = new MetadataLoadContext(resolver);

Assembly coreAssembly = mlc.LoadFromAssemblyName("System.Runtime");
Type openNullableType = coreAssembly.GetType("System.Nullable`1")!;

Assert.Null(Nullable.GetUnderlyingType(openNullableType));
Assert.Null(openNullableType.GetNullableUnderlyingType());
}

[Fact]
public static void GetValueRefOrDefaultRef_WithValue()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2466,7 +2466,7 @@ public override string? FullName

public sealed override bool HasSameMetadataDefinitionAs(MemberInfo other) => HasSameMetadataDefinitionAsCore<RuntimeType>(other);

internal bool IsNullableOfT => Nullable.GetUnderlyingType(this) != null;
internal bool IsNullableOfT => GetNullableUnderlyingType() is not null;

public override bool IsSZArray
{
Expand Down
Loading