In order to change a control by another thread, I need to invoke a delegate to change the control However, it is throwing a TargetParameterCountException:
private void MethodParamIsObjectArray(object[] o) {}
private void MethodParamIsIntArray(int[] o) {}
private void button1_Click(object sender, EventArgs e)
{
// This will throw a System.Reflection.TargetParameterCountException exception
Invoke(new Action<object[]>(MethodParamIsObjectArray), new object[] { });
// It works
Invoke(new Action<int[]>(MethodParamIsIntArray), new int[] { });
}
Why does invoking with MethodParamIsObjectArray throw an exception?
This is due to the fact that the Invoke method has a signature of:
object Invoke(Delegate method, params object[] args)
The params keyword in front of the args parameter indicates that this method can take a variable number of objects as parameters. When you supply an array of objects, it is functionally equivalent to passing multiple comma-separated objects. The following two lines are functionally equivalent:
Invoke(new Action<object[]>(MethodParamIsObjectArray), new object[] { 3, "test" });
Invoke(new Action<object[]>(MethodParamIsObjectArray), 3, "test");
The proper way to pass an object array into Invoke would be to cast the array to type Object:
Invoke(new Action<object[]>(MethodParamIsObjectArray), (object)new object[] { 3, "test" });
Invoke expects an array of objects containing the parameter values.
In the first call, you aren't providing any values. You need one value, which confusingly needs to be an object array itself.
new object[] { new object[] { } }
In the second case, you need an array of objects containing an array of integers.
new object[] { new int[] { } }
Related
Consider this code sample from a WinForms app:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
object[] parms = new object[1];
parms[0] = "foo";
DoSomething(parms);
}
public static string DoSomething(object[] parms)
{
Console.WriteLine("Something good happened");
return null;
}
}
It works as expected, when you click button1 it prints "Something good happened" to the console.
Now consider this code sample, which is the same except that it invokes DoSomething using reflection:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
object[] parms = new object[1];
parms[0] = "foo";
System.Reflection.MethodInfo mi = typeof(Form1).GetMethod("DoSomething");
mi.Invoke(null, parms);
}
public static string DoSomething(object[] parms)
{
Console.WriteLine("Something good happened");
return null;
}
}
It throws an System.ArgumentException on the line mi.Invoke(null, parms) (Object of type 'System.String' cannot be converted to type 'System.Object[]'.)
parms is clearly an object array, and DoSomething's method signature is clearly expecting an object array. So why is invoke pulling the first object out of the array and trying to pass that instead?
Or is something else going on that I'm not understanding?
MethodInfo.Invoke is expecting an object array, where each object in the object array corresponds to an argument to the method. The first argument in the object array is the first argument, the second object in the array the second method, etc.
Since you want the first argument to your method to be an object[], you need to ensure that the first object in the object array you pass to MethodInfo.Invoke is an object array that represents the array that DoSomething should use.
object[] parms = new object[1];
parms[0] = "foo";
with:
public static string DoSomething(object[] parms)
that's the problem; the first parameter is not a string - it is an object[]. The object[] that you pass to Invoke represents each parameter in turn, so an object[] of length 1 with a string would match static string DoSomething(string s), but does not match your method. Either change the signature, or wrap the value. Frankly, I suggest changing the signature is the better idea here, but:
parms[0] = new object[] { "foo" };
would also work
MethodInfo.Invoke expects an array of objects as parameter to pass multiple arguments to the functions, each object in the array will be a different parameter.
As your function also expects an object array you are passing as argument not an object array but an string.
You must wrap that array into another array, in this way the Invoke will unwrap the first array and use the inner array as the first parameter for the call.
mi.Invoke(null, new object[]{ parms });
parms is clearly an object array, and DoSomething's method signature is clearly expecting an object array.
Yes it is expecting an object array. But when you pass this:
object[] parms = new object[1];
You are saying these are the arguments for DoSomething so parms[0] is the first argument to DoSomething and if there were more items in parms then parms[1] will be the 2nd argument and so on.
Clearly the first argument for DoSomething is not a string (parms[0]) so you get this error:
It throws an System.ArgumentException on the line mi.Invoke(null, parms) (Object of type 'System.String' cannot be converted to type 'System.Object[]'.)
Yesterday I wanted to create an ExceptionHandler class to which for example we could pass:
the exception
the method where that happened
the parameters given to that method
The ExceptionHandler would implement a custom business logic and re-execute automatically (or not) the method with the same parameters.
The closest solution I found was by using a delegate like this
private void button1_Click(object sender, EventArgs e)
{
InvokeMyMethod(Test, "string", 1);
InvokeMyMethod(TestN, "other string", 3, Color.Red);
}
public delegate void MyMethodDelegate(params object[] args);
public void InvokeMyMethod(MyMethodDelegate method, params object[] args)
{
method.DynamicInvoke(args);
}
public void Test(params object[] args)
{
if (args.Length < 2) return;
MessageBox.Show(string.Format("{0}{1}", args[0], args[1]));
}
public void TestN(params object[] args)
{
// or, whatewer
if (args.Length < 3) return;
MessageBox.Show(string.Format("{0}{1}{2}", args[0], args[1], args[2]));
}
However this solution is still not perfect because of the methods' signature. I don't want all my methods receive a unique parameter params object[] args, moreover this is often not possible directly for example with all UI related events (Winform, ASP.NET, WPF or whatever).
So I'd like to know if there is a way to pass one by one arguments to a delegate and then execute the function only when we are ready?
For example here is an imaginary code with non-existing methods AddArgument and Execute
public void InvokeMyMethod(MyMethodDelegate method, params object[] args)
{
foreach(var arg in args)
{
method.AddArgument(arg);
}
method.Execute();
}
By this way all our functions could keep their original signature and receive multiple arguments instead of an array.
Your original code isn't far off at all. You can get rid of requiring all the called methods have the same signature as long as you make InvokeMyMethod just take the abstract Delegate class and you explicitly specify the delegate type when you call the function (it won't infer the type). We can take advantage of the fact that there are generic delegate types in C# so we don't need to declare every possible set of method signatures that we want to use.
If you wanted to call functions with a return value you'd need to use Func<> in place of Action<>.
private void button1_Click(object sender, EventArgs e)
{
InvokeMyMethod((Action<string,int>) Test, "string", 1);
InvokeMyMethod((Action<string,int,Color>) TestN, "other string", 3, Color.Red);
}
public void InvokeMyMethod(Delegate method, params object[] args)
{
method.DynamicInvoke(args);
}
public void Test(string s, int i)
{
MessageBox.Show(string.Format("{0}{1}", s, i));
}
public void TestN(string s, int i, Color c)
{
// or, whatewer
MessageBox.Show(string.Format("{0}{1}{2}", s, i , c);
}
I have a class which contains an empty constructor and one that accepts an array of objects as its only parameter. Something like...
public myClass(){ return; }
public myClass(object[] aObj){ return; }
This is the CreateInstance() method call that I use
object[] objectArray = new object[5];
// Populate objectArray variable
Activator.CreateInstance(typeof(myClass), objectArray);
it throws System.MissingMethodException with an added message that reads
"Constructor on type 'myClass' not found"
The bit of research that I have done has always shown the method as being called
Activator.CreateInstance(typeof(myClass), arg1, arg2);
Where arg1 and arg2 are types (string, int, bool) and not generic objects.
How would I call this method with only the array of objects as its parameter list?
Note: I have tried adding another variable to the method signature. Something like...
public myClass(object[] aObj, bool notUsed){ return; }
and with this the code executed fine.
I have also seen methods using reflection which were appropriate but I am particularly interested in this specific case. Why is this exception raised if the method signature does in fact match the passed parameters?
Cast it to object:
Activator.CreateInstance(yourType, (object) yourArray);
Let's say you have constructor:
class YourType {
public YourType(int[] numbers) {
...
}
}
I believe you would activate like so by nesting your array, the intended parameter, as a item of the params array:
int[] yourArray = new int[] { 1, 2, 4 };
Activator.CreateInstance(typeof(YourType ), new object[] { yourArray });
Is it possible in C# to accept a params argument, then pass it as params list to another function? As it stands, the function below will pass args as a single argument that is an array of type object, if I'm not mistaken. The goal here is self evident.
//ScriptEngine
public object[] Call(string fnName, params object[] args)
{
try{
return lua.GetFunction(fnName).Call(args);
}
catch (Exception ex)
{
Util.Log(LogManager.LogLevel.Error, "Call to Lua failed: "+ex.Message);
}
return null;
}
The lua.GetFunction(fnName).Call(args); is a call to outside of my code, it accepts param object[].
If the signature of the Call method you're calling accepts a params object[] args then you are mistaken. It doesn't consider args a single object of thpe object, to be wrapped in another array. It considers it the entire argument list, which is what you want. It'll work just fine exactly as it stands.
Yes, it is possible.In your case args actualy an array of objects.Your Call method should take params object[] or just an array of objects as parameter.
You don't need to pass more than one argument to params.You can pass an array directly.For example this is completely valid:
public void SomeMethod(params int[] args) { ... }
SomeMethod(new [] { 1, 2, 3 });
It is possible to pass the array to another function:
void Main()
{
int[] input = new int[] {1,2,3};
first(input); //Prints 3
}
public void first(params int[] args)
{
second(args);
}
public void second(params int[] args)
{
Console.WriteLine(args.Length);
}
I use a thread to execute some process on a machine. Eventually, the progress is reported back in an other thread. To update the GUI with the status of the process, I use a delegate like this:
public delegate void UpdateProgressDelegate(string description, int scriptnumber);
public void UpdateProgress(string description, int scriptnumber) {
if (treeView.InvokeRequired) {
treeView.Invoke(new UpdateProgressDelegate(UpdateProgress), description, scriptnumber);
return;
}
// Update the treeview
}
And to call this delegate I use:
form.UpdateProgress("Ready", 3);
When the Invoke is called, I get a TargetParameterCountException: Parameter count mismatch.
I thought I could fix this by placing the string and int parameters in a single object like this:
public delegate void UpdateProgressDelegate(object[] updateinfo);
public void UpdateProgress(object[] updateinfo) {
string description = (string) updateinfo[0];
int scriptnumber = (int) updateinfo[1];
if (treeView.InvokeRequired) {
treeView.Invoke(new UpdateProgressDelegate(UpdateProgress), new object[] { description, scriptnumber });
return;
}
// Update the treeview
}
And to call it I use:
form.UpdateProgress(new object[] {"Ready", 3});
But this doesn't work either. I keep getting the same TargetParameterCountException. Any ideas how I could fix this? Thanks in advance!
I would say: do it the easy way:
treeView.Invoke((MethodInvoker)delegate {
UpdateProgress(description, scriptnumber);
});
or (equally):
treeView.Invoke((MethodInvoker) () => UpdateProgress(description, scriptnumber));
This gives you static-checking at the compiler, and IIRC MethodInvoker is checked explicitly, and called with Invoke() rather than DynamicInvoke(), making it faster too.
Re why it doesn't work; in your example with:
public delegate void UpdateProgressDelegate(object[] updateinfo);
you are actually passing two parameters; to disambiguate and pass a single array to a params here, you need to double-wrap it:
treeView.Invoke(new UpdateProgressDelegate(UpdateProgress),
new object[] { new object[] {description, scriptnumber }});
Basically, the outer array is the "array of all the parameters", which contains a single element, which is the array that we wan't to pass as the first parameter (updateinfo).
This should work:
public delegate void UpdateProgressDelegate(string description, int scriptnumber);
public void UpdateProgress(string description, int scriptnumber) {
if (treeView.InvokeRequired) {
treeView.Invoke(new UpdateProgressDelegate(UpdateProgress), new object[] { description, scriptnumber });
return;
}
// Update the treeview
}