调用 MethodInfo.MakeGenericMethod 时出现此关键错误意味着什么?

发布于 2025-01-08 03:50:30 字数 2612 浏览 0 评论 0 原文

今天,我从一些旧的动态转换代码中得到了这个错误(我更改了最后一行并省略了堆栈跟踪的其余部分):

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)

完整的类

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 是一个 Dictionary> 包含已编译的 lambda 表达式,这些表达式是根据需要延迟构建的,用于将 Message 事件(基于 EventArgs 的简单自动属性类)转换为其他中使用的字符串格式系统。但我认为这些都不重要。我认为导致此问题的唯一必要代码是:

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;
    }
}

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;
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

赠我空喜 2025-01-15 03:50:30

看起来框架未能正确锁定 MakeGenericMethod 的内部。

当调用 MakeGenericMethod 时,框架应该使用指定的泛型参数创建该方法的新版本,或者如果之前已使用相同的泛型参数类型来创建该泛型方法,那么它应该返回先前的-生成的方法。看起来您遇到了一个边缘情况,在多个线程上调用 MakeGenericMethod 可能会导致竞争条件,两个线程都认为该方法尚未生成并继续生成它,然后随后发生冲突存储生成的方法以供将来调用。

也就是说,在这种情况下,看起来一切都被锁住了,所以我也不完全相信这就是问题所在。

我会将其作为错误向 MSFT 归档,除非其他人可以解释这是如何预期的行为。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文