Repository navigation
Regression with generics from .NET 8 To .Net 10 #129692
Copy link
Copy link
Closed
Description
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.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Type
Projects
- StatusShow more project fieldsNo status
Activity
dotnet-policy-service commentedon Jun 22, 2026
Tagging subscribers to this area: @dotnet/area-system-collections
See info in area-owners.md if you want to be subscribed.
EgorBo commentedon Jun 22, 2026
Most likely a regression from either #97590 or #98754 cc @huoyaoyuan
copilot's suggested fix:
dotnet-policy-service commentedon Jun 22, 2026
Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.
huoyaoyuan commentedon Jun 23, 2026
The exception is happening in
KeyValuePair<Id<Target>, Target>.GetHashCode.I'm not sure whether the Copilot fix is related.GetFieldTypeHandleThrowingwas already used before #97590.Looks like exposed by #98754. The old implementation wasn't calling overridden
GetHashCodeat all.Interestingly, the code will not work correctly on .NET 8 either. Replace the
StringComparer.OrdinalwithStringComparer.OrdinalIgnoreCase, and test the following code:The assert will fail, despite
"A"and"a"are treated as the same key andactual.Add(new Id<Target>("a"), target)fails.Fix ValueType.GetHashCode for nested generic field (#129728)
Fix ValueType.GetHashCode for nested generic field (#129728)