tags:

views:

346

answers:

1

I can't seem to figure out why I am getting an InvalidCastException running the following code:

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

Exception Info:

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..

Any assistance would be appreciated =) This code seems to work with anything I throw at it except a KeyValuePair<>.

Update: It appears this condition exists for any struct. I hadn't noticed KeyValuePair<> was a struct and so was only testing with classes. I still don't understand why this is the case though.

Update 2: Simon's answer helped confirm this behavior is unexpected, however implementing a custom type won't work for what I'm trying to do. I am trying to implement an extension method on IEnumerable<> to execute a delegate Asynchronously for each item. I noticed the error running tests against a generic Dictionary object.

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

I would rather not limit the method with a constraint on T to ensure only classes are used so I have refactored the method as follows to get around the problem with BeginInvoke but I have not worked with the TreadPool directly before and would like to make sure I am not missing anything important.

    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  A: 

Odd, seems to be some sort of bug in .NET (C#?) with marshalling the argument to the worker thread.

If you implement IConvertable on the passed struct:

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

It runs fine. The passed conversionType doesnt pass .Equal(), IsAssignableFrom(), or anything else I tried except GUID comparison, which is probably related to why it asks for an IConvertable in the first place.

EDIT: A simple workaround is to use closures to pass the parameter:

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

Of course, if you need the results, you will need to store the IAsyncResults, which will probably have the same issue as parameters, in the other direction. As an alternative, you could add them to a collection when they are complete, but the locking gets a bit weird:

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);
Simon Buchan
Thanks Simon, I'm glad I wasn't just overlooking something silly. I assume its a bug in the framework causing the type checks to fails resulting in check for the IConvertable interface. Unfortunately implementing a custom type is not an option for what I am trying to do. I more detail to come.
Venr