Sporadic InvalidCastException, possible hash collision in ObjectCreator.cache
Describe the bug Sometimes the test suite I have throws a very strange InvalidCastException:
System.InvalidCastException : Unable to cast object of type 'CsvHelper.Configuration.MemberMap`2[OtherRecordClass,SomeEnumType]' to type 'CsvHelper.Configuration.MemberMap`2[MyRecordClass,System.Nullable`1[System.DateTime]]'.
Stack Trace:
at CsvHelper.Configuration.ClassMap`1.Map[TMember](Expression`1 expression, Boolean useExistingMap)
at MyRecordClassDtoMap..ctor()
at lambda_method5587(Closure, Object[])
at CsvHelper.ObjectCreator.CreateInstance(Type type, Object[] args)
at CsvHelper.ObjectResolver.<>c__DisplayClass17_0.<.cctor>b__1(Type type, Object[] args)
at CsvHelper.ObjectResolver.Resolve(Type type, Object[] constructorArgs)
at CsvHelper.ObjectResolver.Resolve[T](Object[] constructorArgs)
at CsvHelper.CsvContext.RegisterClassMap[TMap]()
My code involved in this is the following:
using var csv = new CsvHelper.CsvReader(reader, config);
csv.Context.RegisterClassMap<MyRecordClassDtoMap>(); // <-- exception here
And MyRecordClassDtoMap is this:
public class MyRecordClassDtoMap : ClassMap<MyRecordClass>
{
public MyRecordClassDtoMap()
{
AutoMap(CultureInfo.CurrentCulture);
Map(m => m.OptionalDateTime1).Convert(CellConverter.Instance.DateTimeNullableDbase); // <-- this line is the `at MyRecordClassDtoMap..ctor()` in the stack trace, the exception is in the Map call
Map(m => m.OptionalDateTime2).Convert(CellConverter.Instance.DateTimeNullableDbase);
}
}
Inspecting the code in the CsvHelper the only way I could imagine such a problem occurring is for there to be a rare hash collision in ObjectCreator's cache causing it to wrongly use a cached instance creation function for a different type than then one needed.
My code does not use any custom IObjectResolver or other ObjectResolver instances.
To Reproduce Sadly, I was unable to locally reproduce the problem. But it occurs somewhat often on my CI runner.
Additional context
I tried replacing the ObjectResolver.Current with a new instance done analogous to how static ObjectResolver() assigns it, but using a customised copy of ObjectCreator, which uses a string[] as its cache key. The strings are the AssemblyQualifiedNames of the types, and the Dictionary is given a custom equality comparer to handle hashing and comparing string arrays.
Since then I have not encountered the error. However, at the moment each test class that needs it, does that initialization separately (I'm still not sure where is a good central place to replace ObjectResolver.Current for the tests), so it's not certain that the improvement is not just due to refreshing the ObjectResolver.Current value for each test class.
Still, I think it may be a good idea to change ObjectCreator.cache to avoid possible hash collisions.
Try updating to 33.1.0 and see if that fixes it. I merged a PR that theoretically should fix this.