2009-02-27 49 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

相關問題