Skip to content

Regression with generics from .NET 8 To .Net 10 #129692

Description

@AndreasHeisel

Description

I have a generic readonly record struct. It has a single instance member of type System.String. I use it as a strongly typed identifier to various types of objects. To make this customizable, the target object must implement a generic interface with a static virtual property of type StringComparer.

Reproduction Steps

Run the single unit test:

public readonly record struct Id<T>(string Value) : IEquatable<Id<T>> where T : ITarget<T>
{
	public static StringComparer Comparer { get; } = T.IdentifierComparer;

	public override int GetHashCode()
	{
		return Comparer.GetHashCode(Value);
	}

	public bool Equals(Id<T> other)
	{
		return Comparer.Equals(Value, other.Value);
	}

	public override string ToString()
	{
		return Value;
	}
}


public interface ITarget<T> where T : ITarget<T>
{
	static virtual StringComparer IdentifierComparer => StringComparer.Ordinal;

	public Id<T> Id { get; }
}


public record Target(Id<Target> Id) : ITarget<Target>
{
}


[TestClass]
public class Test
{
	[TestMethod]
	public void AssertDictionary()
	{
		var actual = new Dictionary<Id<Target>, Target>();
		var expected = new Dictionary<Id<Target>, Target>();

		var target = new Target(new("1"));
		actual.Add(target.Id, target);
		expected.Add(target.Id, target);

		CollectionAssert.AreEquivalent(expected, actual);
	}
}

Expected behavior

No exception

Actual behavior

Message: 
Test method IdTest.Test.AssertDictionary threw exception:
System.NullReferenceException: Object reference not set to an instance of an object.

  Stack Trace: 
Id`1.get_Comparer() line 7
Id`1.GetHashCode() line 12
ValueType.GetHashCode()
Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
Dictionary`2.set_Item(TKey key, TValue value)
CollectionAssert.GetElementCounts[T](IEnumerable`1 collection, IEqualityComparer`1 comparer, Int32& nullCount)
CollectionAssert.FindMismatchedElement[T](IEnumerable`1 expected, IEnumerable`1 actual, IEqualityComparer`1 comparer, Int32& expectedCount, Int32& actualCount, Object& mismatchedElement)
CollectionAssert.AreEquivalent[T](IEnumerable`1 expected, IEnumerable`1 actual, IEqualityComparer`1 comparer, String message)
CollectionAssert.AreEquivalent(ICollection expected, ICollection actual)
Test.AssertDictionary() line 22
MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)

Regression?

This worked well in .NET8.

Known Workarounds

None

Configuration

.NET 10, Windows11, x64

Other information

I get a NullReferenceException if the struct reads the interface's static property.

Using the debugger, I see that the runtime uses System.__Canon instead of my type.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions