I need to cast an Action<string>
to Action<object>
. While this is type-unsafe in general, in my case it will always be called with a string. I'm getting this error:
Unable to cast object of type 'System.Action
1[System.String]' to type 'System.Action
1[System.Object]'.
Any clues? Reflection is fair game. Wrapping the one delegate into another is not.
UPDATE: I created a new question at Creating an performant open delegate for an property setter or getter with a better explanation of my issue, and a solution using wrapping which I want to im开发者_JAVA技巧prove on
First, it's not safe. Something that can accept any string can't (necessarily) accept any object. Think of a example method:
void method(String s)
{
s.Trim();
}
Obviously, this would fail if s were an object without Trim
.
Technically, this means Action
is contravariant on T. You could assign an Action<string>
to a hypothetical Action<SubclassString>
reference, but string
can not be subclassed.
It's true that C# allows regular unsafe casts (e.g. object
itself to string
), at the risk of a InvalidCastException
. However, they chose not to implement the infrastructure for unsafe delegate casts.
EDIT: I don't know of a way to do it without a wrapper.
I know your post specifies that wrapping in another delegate is not an option but unfortunately that's really your best choice here. The CLR simply does not allow for a cast between delegates in this direction. No amount of reflection can fix this either. Can you elaborate further on why this is not an option?
The reason why is that it creates type safety issues because the caller can pass any object into the Action<object>
while the Action<string>
can only handle strings. Even if your code only passes in a string
the CLR cannot guarantee this and hence does not allow for the unsafe conversion.
The next best option I can think of is change the original method which is being wrapped in a Action<string>
from taking a parameter of type string
to one that takes object
. Then let it manually verify the type is string
. For example
// Original Version
void Method(string str) {
// Operate on the string
}
// Modified version
void Method(object obj) {
string str = (string)obj;
// operate on the string
}
Sorry for the late bump, but I was looking for a way to do this today and stumbled upon this post. Really, the only simple way I found to do it was to wrap Action<string>
within a new Action<object>
. In my case, I was pushing my Actions into a Concurrent Dictionary, and then retrieving them by type.
Effectively, I was processing a queue of messages where actions could be defined to handle messages with a particular typed input.
ConcurrentDictionary<Type, Action<object>> _actions = new ConcurrentDictionary<Type, Action<object>>();
Action<string> actionStr = s => Console.WriteLine(s);
var actionObj = new Action<object>(obj => {
var castObj = (V)Convert.ChangeType(obj, typeof(V)); actionStr(castObj);
} );
_actions.TryAdd(typeof(string), actionObj);
Since reflection is fair game, I will suggest the following. You can get away from the casting problem if you keep track of an Object, MethodInfo, and possibly the parameter you will execute against. An example of how the concept works is as follows:
Action<string> s;
Action<object> o;
object sTarget = s.Target;
object oTarget = o.Target;
MethodInfo sMethod = s.Method;
MethodInfo oMethod = o.Method;
// Time to invoke in a later time.
sMethod.Invoke(sTarget, new object[] { strVal });
oMethod.Invoke(oTarget, new object[] { objVal });
The only danger with this is the possibility that the wrong parameter type will be executed against the wrong method. You might need to do some bookkeeping here to prevent this. But, in your case, since there is a guarantee that a string is going to be passed (and since a string is always an object), this should always succeed.
精彩评论