CsvHelper icon indicating copy to clipboard operation
CsvHelper copied to clipboard

Sporadic InvalidCastException, possible hash collision in ObjectCreator.cache

Open delta-emil opened this issue 1 year ago • 1 comments

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.

delta-emil avatar Aug 08 '24 15:08 delta-emil

Try updating to 33.1.0 and see if that fixes it. I merged a PR that theoretically should fix this.

JoshClose avatar Jun 06 '25 15:06 JoshClose