What does this key error mean when calling MethodI

2019-04-12 23:20发布

问题:

Today I got this error from some old dynamic casting code (I have changed the last line and left out the rest of the stack trace):

Item has already been added. 
Key in dictionary: 
   'Int32 Count[Object](System.Collections.Generic.IEnumerable`1[System.Object])'  
Key being added: 
   'Int32 Count[Object](System.Collections.Generic.IEnumerable`1[System.Object])' 
---> System.ArgumentException: Item has already been added. 
     Key in dictionary: 
         'Int32 Count[Object](System.Collections.Generic.IEnumerable`1[System.Object])'  
     Key being added: 
         'Int32 Count[Object](System.Collections.Generic.IEnumerable`1[System.Object])'
  at System.Reflection.CerHashtable`2.Insert(K[] keys, V[] values, Int32& count, K key, V value)
  at System.Reflection.CerHashtable`2.Preallocate(Int32 count)
  at System.RuntimeType.RuntimeTypeCache.GetGenericMethodInfo(RuntimeMethodHandle genericMethod)
  at System.RuntimeType.GetMethodBase(RuntimeTypeHandle reflectedTypeHandle, RuntimeMethodHandle methodHandle)
  at System.Reflection.RuntimeMethodInfo.MakeGenericMethod(Type[] methodInstantiation)
  at MyNamespace.CommunicationExtensions.BuildMessage[T](T obj)

The full class

public static class CommunicationExtensions {
    static readonly object lockobj = new object();
    public static bool CanBuildMessage<T>(this T obj) where T: class {
        return obj != null && (MessageFactory.MessageBuilders.ContainsKey(obj.GetType()));
    }
    public static string BuildMessage<T>(this T obj) {
        lock (lockobj) {
            Delegate d;
            var type = obj.GetType();

            if (MessageFactory.MessageBuilders.TryGetValue(type, out d)) {
                var castMethod = typeof(CommunicationExtensions).GetMethod("Cast").MakeGenericMethod(type);
                var castedObject = castMethod.Invoke(null, new object[] { obj });

                return d.DynamicInvoke(castedObject) as string;
            }
        }
        return null;
    }
    public static T Cast<T>(object o) {
        return (T)o;
    }
}

MessageFactory.MessageBuilders is a Dictionary<Type,Func<Type,string>> containing compiled lambda expressions that are lazily built as needed to convert the Message events (simple auto-property classes based on EventArgs) into a string format used in other systems. I don't think any of that matters though. I think the only code necessary to cause this issue is:

public static class CastError{
    public static void GetCast<T>(this T obj) {
        var type = obj.GetType();
        var castMethod = typeof(CastError).GetMethod("Cast").MakeGenericMethod(type);
        //...
    }
    public static T Cast<T>(object o) {
        return (T)o;
    }
}

回答1:

What it looks like is a failure in the framework to lock properly in the internals of MakeGenericMethod.

When MakeGenericMethod is called, the framework is supposed to either create a new version of the method with the generic parameters specified, or if the same generic parameter type(s) have been used before to create that generic method then it should return the previously-generated method. It looks like you hit an edge case where calling MakeGenericMethod on multiple threads can result in a race condition where both threads think that the method has not yet been generated and go ahead and generate it, then subsequently conflict in storing the generated methods for future calls.

That said, in this case it looks like it's all in a lock, so I am not fully convinced that this is the problem either.

I'd file it with MSFT as a bug, unless someone else can explain how this is expected behavior.