2009-02-27 64 views
1

我似乎无法找出为什么我收到一个InvalidCastException运行下面的代码:CastException试图调用操作<KeyValuePair <>>委托异步

var item = new KeyValuePair<string, string>("key", "value"); 

Action<KeyValuePair<string, string>> kvrAction = 
    kvr =>Console.WriteLine(kvr.Value); 

var result = kvrAction.BeginInvoke(item, null, null); 
kvrAction.EndInvoke(result); 

异常信息:

Test method Utilities.Tests.IEnumerableExtensionTests.ProveDelegateAsyncInvokeFailsForKeyValuePair threw exception: System.Runtime.Remoting.RemotingException: The argument type '[key, value]' cannot be converted into parameter type 'System.Collections.Generic.KeyValuePair`2[System.String,System.String]'. 
---> System.InvalidCastException: Object must implement IConvertible.. 

任何援助将不胜感激=)此代码似乎与我抛出的任何东西,除了一个KeyValuePair <>。

更新:看起来这种情况存在于任何结构中。我没有注意到KeyValuePair <>是一个结构,所以只能用类进行测试。我仍然不明白为什么会出现这种情况。

更新2:Simon的答案有助于确认此行为是意外的,但是实施自定义类型对于我正在尝试执行的操作无效。我试图在IEnumerable <>上为每个项目异步执行一个委托来实现一个扩展方法。我注意到运行测试针对通用Dictionary对象的错误。

public static IEnumerable<T> ForEachAsync<T>(this IEnumerable<T> input, Action<T> act) 
    { 
     foreach (var item in input) 
     { 
      act.BeginInvoke(item, new AsyncCallback(EndAsyncCall<T>), null); 
     } 

     return input; 
    } 

    private static void EndAsyncCall<T>(IAsyncResult result) 
    { 
     AsyncResult r = (AsyncResult)result; 
     if (!r.EndInvokeCalled) 
     { 
      var d = (Action<T>)((r).AsyncDelegate); 
      d.EndInvoke(result); 
     } 
    } 

我宁愿不限制与T约束的方法,以确保只有类用于所以我重构方法如下绕过与BeginInvoke的问题,但我还没有与TreadPool直接工作过并希望确保我不会错过任何重要的事情。

public static IEnumerable<T> ForEachAsync<T>(this IEnumerable<T> input, Action<T> act) 
    { 
     foreach (var item in input) 
      ThreadPool.QueueUserWorkItem(obj => act((T)obj), item); 

     return input; 
    } 

回答

1

奇怪的,似乎是.NET(C#?)中的一些错误,将参数编组到工作线程。

如果您在通过结构实现IConvertable:

struct MyPair<TKey, TValue> : IConvertable 
{ 
    public readonly TKey Key; 
    public readonly TValue Value; 

    public MyPair(TKey key, TValue value) 
    { 
     Key = key; 
     Value = value; 
    } 

    // I just used the smart-tag on IConvertable to get all these... 
    // public X ToX(IFormatProvider provider) { throw new InvalidCastException(); } 

    ... 

    public object ToType(Type conversionType, IFormatProvider provider) 
    { 
     if (typeof(MyPair<TKey, TValue>).GUID == conversionType.GUID) 
      return this; 
     throw new InvalidCastException(); 
    } 
} 

运行良好。传递的转换类型不传递.Equal(),IsAssignableFrom()或其他我试过的东西,除了GUID比较,这可能与它首先要求IConvertable的原因有关。

编辑:一个简单的解决方法是使用闭包来传递参数:

var data = new Dictionary<string, string> { 
    { "Hello", "World" }, 
    { "How are", "You?" }, 
    { "Goodbye", "World!" } 
}; 
foreach (var pair in data) 
{ 
    var copy = pair; // define a different variable for each worker 
    Action worker =() => Console.WriteLine("Item {0}, {1}", copy.Key, copy.Value); 
    worker.BeginInvoke(null, null); 
} 

当然,如果你需要的结果,您将需要存储IAsyncResults,这将可能有相同的问题参数,在另一个方向。作为替代方案,你可以将它们添加到当他们完成的集合,而是锁定变得有点怪异:

var data = new Dictionary<string, string> { 
    { "Hello", "World" }, 
    { "How are", "You?" }, 
    { "Goodbye", "World!" } 
}; 

var results = new List<KeyValuePair<string, string>>(); 
var pending = 0; 
var done = new ManualResetEvent(false); 

var workers = new List<Action>(); 
foreach (var pair in data) 
{ 
    ++pending; 
    var copy = pair; // define a different variable for each worker 
    workers.Add(delegate() 
    { 
     Console.WriteLine("Item {0}, {1}", copy.Key, copy.Value); 
     lock (results) 
      results.Add(new KeyValuePair<string, string>("New " + copy.Key, "New " + copy.Value)); 
     if (0 == Interlocked.Decrement(ref pending)) 
      done.Set(); 
    }); 
} 

foreach (var worker in workers) 
    worker.BeginInvoke(null, null); 

done.WaitOne(); 

foreach (var pair in results) 
    Console.WriteLine("Result {0}, {1}", pair.Key, pair.Value); 
+0

感谢西蒙,我很高兴我不只是忽视的东西傻了。我认为它在框架中导致类型检查失败导致检查IConvertable接口的错误。不幸的是,实现一个自定义类型不是我想要做的选项。我更详细的来。 – Venr 2009-02-27 16:48:34

相关问题