我似乎无法弄清楚为什么我在运行以下代码时遇到InvalidCastException:
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&lt;&gt;。
更新:对于任何结构,似乎存在这种情况。我没有注意到KeyValuePair&lt;&gt;是一个结构,所以只用类测试。我仍然不明白为什么会这样。
更新2:Simon的回答有助于确认此行为是意外的,但实现自定义类型不适用于我正在尝试的操作。我正在尝试在IEnumerable&lt;&gt;上实现扩展方法为每个项目异步执行委托。我注意到针对通用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;
}
答案 0 :(得分:1)
奇怪,似乎是.NET中的某种错误(C#?),它将参数编组到工作线程中。
如果在传递的struct上实现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();
}
}
运行正常。传递的conversionType没有传递.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);