C# - Delegate with any amount of custom parameters [duplicate] - c#

This question already has answers here:
How can I design a class to receive a delegate having an unknown number of parameters?
(6 answers)
C# How to call a method with unknown number of parameters
(3 answers)
Closed 5 years ago.
I want a delegate that I can store in a variable for later use that has custom amounts of custom parameters. What I mean by that, is that I want to pus it different methods with different return types and different arguments. For example:
public double Sum (double a, double b) {return a + b;}
public char GetFirst (string a) {return a[0];}
public bool canFlipTable (object[] thingsOnIt) {return thingsOnIt.Length <= 3;}
DoTheThing<double> thing1 = new DoTheThing<double>(Sum);
DoTheThing<char> thing2 = new DoTheThing<char>(GetFirst);
DoTheThing<bool> thing3 = new DoTheThing<bool>(canFlipTable);
thing1.Call(10.3, 5.6); //15.9
thing2.Call("Hello World"); //'H'
thing3.Call(new object[] {new Lamp(), new Laptop(), new CoffeMug()}); //true
I figured out the return value and the call method already, but I'm having a problem with storing the methods
If I use "public DoTheThing(Action method)" it says, that the arguments doesn't match
I even tried with a delegate that had "params object[] p" as arguments, but it didn't work either
EDIT:
I forgot to tell, the method WILL always have a return type and at least 1 parameter
EDIT 2:
My goal is creating a wrapper class, that caches outputs from very expensive methods and if the same thing gets called again, it returns the cached value.
Of course I could solve this with an interface, but I want to do this with classes that I can't simply edit and I want to make this felxible too, so having the cache at the same place where I call the method is not an option either.
My code sofar:
public class DoTheThing <T>
{
public delegate T Method(params object[] parameters);
Func<T> method;
ParameterInfo[] pInfo;
public DoTheThing (Method method)
{
this.method = method;
Type type = typeof(Method);
MethodInfo info = type.GetMethod ("Invoke");
if (info.ReturnType != typeof(T)) {
throw new Exception ("Type of DoTheThing and method don't match");
}
pInfo = info.GetParameters ();
}
public T Call (params object[] parameters) {
if (parameters.Length != pInfo.Length) {
throw new Exception ("Wrong number of arguments, " + parameters.Length + " instead of " + pInfo.Length);
return default(T);
}
for (int i = 0; i < parameters.Length; i++) {
if (pInfo[i].ParameterType != parameters[i].GetType()) {
throw new Exception ("Wrong parameter: " + parameters [i].GetType () + " instead of " + pInfo [i].ParameterType + " at position: " + i);
return default(T);
}
}
return (T)method.DynamicInvoke (parameters);
}
}

Before trying to figure how to do it, I would really question the problem that leads me to have such a kind of delegate. I would bet if I knew the context better, there would be a solution that would eliminate your requirement.
Having that said, delegates are classes that inherit from MulticastDelegate. In fact, when you declare a delegate, you are creating a new class type with MulticastDelegate as its base class. That means the following code works:
public static double Sum(double a, double b)
{
return a + b;
}
public static string SayHello()
{
return "Hello";
}
static void Main(string[] args)
{
MulticastDelegate mydel = new Func<double, double, double>(Sum);
var ret = mydel.DynamicInvoke(1, 2);
System.Console.WriteLine(ret);
mydel = new Func<string>(SayHello);
ret = mydel.DynamicInvoke();
System.Console.WriteLine(ret);
mydel = new Func<string, int, string> ((s, i) => {
return $"Would be {s}, {i} times";
});
ret = mydel.DynamicInvoke("Hello", 5);
System.Console.WriteLine(ret);
}
Because "mydel" variable is of the base class type (MulticastDelegate), we can actually use it with any kind of delegate and invoke it with arbitrary parameters. If they don't match the method being invoked, it will throw at runtime.

Related

How to wrap a Func<T1...Tn> with unknown number and type of parameters in C#?

Suppose I have this class:
public class Function {
public int argc; //number of arguments of the function
public float[] argv;
public Func<float> f; //attribute to store a function (e.g. Sin(x) or Pow(a, b))
}
I want to create instances of Function that hold different functions, like Sin(x) or Pow(a, b), but I don't know how to bind an existing function (with any number of arguments) to a Func. Obviously its declaration would not always be Func<float> but Func<float, float>, Func<float, float, float>, etc.
I've looked for Func, delegate, Action but still didn't figure out how to have this "function capsule" that can hold and execute functions with different number of arguments. For simplicity I consider the only input and output type is float.
I'm thinking about using something like Func<List<float>> but I want to know if there is a better option.
I want to suggest an answer that fits more accurately the scenario described by the OP. The key is in the usage of Delegate.DynamicInvoke which lets you pass an indefinite number of arguments to a delegate.
public class Function<TReturn> {
private readonly object[] _argv;
private readonly Delegate _func;
public Function(Delegate func, params object[] args) {
_func = func;
_argv = args;
}
public TReturn Run() {
object v = _func.DynamicInvoke(_argv);
return (TReturn)v;
}
}
And its usage lets you decide dynamically the number of arguments you wish to pass:
var s = new Function<double>((Func<double, double>)(x => Math.Sin(x)), 1 );
Console.WriteLine(s.Run()); // prints 0.8414709848078965
var p = new Function<double>((Func<double, double, double>)((a, b) => Math.Pow(a, b)), 2, 3);
Console.WriteLine(p.Run()); // prints 8
var d = new Function<string>((Func<string, double, string>)((a, b) => a + b.ToString()), "hello, ", 42);
Console.WriteLine(p.Run()); // prints "hello, 42"
Note that type checking is only performed at run-time when calling Function.Run() and not when constructing the Function object because of its dynamic nature. If you know for sure that all passed arguments will always be of the same type, you could enforce that statically by adding a TArg generic type.

Can ref and Out keyword can decide method overloading

I have a question regarding method overlading given below
Fun1(int a);
Fun1(ref int a);
Is this method overloading? IF Yes then WHY and if No then WHY?
As per documentation:
Methods can be overloaded when one method has a ref or out parameter and the other has a value parameter
So the answer to your question is yes, but why?
As per definition of function overloading:
Function overloading (also called method overloading) is a programming concept that allows programmers to define two or more functions with the same name and in the same scope. Each function has a unique signature (or header), which is derived from:
function/procedure name
number of arguments
arguments' type
arguments' order
When you are passing a parameter without using the ref keyword you are actually passing a reference to the variable thus the type of argument is different from that when passed without ref keyword.
public void function(ref int abc)
{
Console.WriteLine("Result Ref: " + abc);
}
public void function(int abc)
{
Console.WriteLine("Result: " + abc);
}
Thus the signature of the above two functions is not the same as the type of argument passed to the both is not the same.
Hope I was able to satisfy you with my answer :).
Also I would like to add another point to the discussion that overloading is not possible in case of using out with one function and ref with another as in this case both the types are considered as same so no overloading in the following case:
public void function(ref int abc)
{
Console.WriteLine("Result Ref: " + abc);
}
public void function(out int abc)
{
abc = 1221;
Console.WriteLine("Result Out: " + abc);
}
1) Fun1(int a);
2) Fun1(ref int a);
1 and 2 are different functions. 1 - pass by value, 2 - pass by reference.
For example:
public static void Func(int i)
{
i++;
Console.WriteLine("int a = {0}", i);
}
public static void Func(ref int i)
{
i++;
Console.WriteLine("ref int a = {0}", i);
}
static void Main(string[] args)
{
int a = 9;
Func(ref a);
// Func(a);
Console.WriteLine("a = {0}", a);
Console.Read();
}
output:
ref int a = 10
a = 10
it means that a is passed by reference and function Func(ref int i) can change its value.
in second case when you call Func(a) in Main function result is:
int a = 10
a = 9
It means that a is passed by value.
You can implement both functions in one class․

Why output is 5? [duplicate]

This question already has answers here:
Multicast delegate of type Func (with return value)?
(3 answers)
Closed 5 years ago.
I am trying to add 2 more functions to my delegate but it seems it doesn't process the functions I added. Following program outputs 5. I expect 10. Why is this the case?
using System;
namespace abc
{
public delegate int Del(int k);
class Class1
{
public int Add2(int value)
{
return value = value + 2;
}
public int Add3(int value)
{
return value = value + 3;
}
}
class Program
{
static void Main(string[] args)
{
Class1 c = new Class1();
Del mydel = c.Add2;
mydel += c.Add3;
mydel += c.Add2;
Console.WriteLine(mydel(3));
Console.ReadLine();
}
}
}
What happens when you call mydel(3) is this:
Add2(3) is called. This returns 3 + 2 = 5, the return value of which is discarded.
Add3(3) is called. This returns 3 + 3 = 6, the return value of
which is discarded.
Add2(3) is called. This returns 3 + 2 = 5, the return value of which is printed.
When you chain delegates, the return value of one is NOT passed as a parameter to a chained one.
Note that the delegates are called in LIFO order.
If you want to see how to actually chain functions together using delegates, see this thread.
Alternatively, as per the linked answer (and as mentioned by Toskr in the comments below), you can use GetInvocationList() and DynamicInvoke() to chain the methods together, but this is a very unusual thing to do:
static void Main()
{
Class1 c = new Class1();
Del mydel = c.Add2;
mydel += c.Add3;
mydel += c.Add2;
int result = 3;
foreach (var func in mydel.GetInvocationList())
{
result = (int)func.DynamicInvoke(result);
}
Console.WriteLine(result);
}

First array element is being passed instead of whole array

It seems that if there is only one parameter of type array on a method
the value of the parameter passed to my LogException() method is not an array anymore.
When there is more than one parameter on a method, or if the one parameter is not an array, it works as expected. But when I try to pass an array, it seems that the first value of the array becomes the parameter that was passed.
All comments are inlined to explain and show the problem. The problem first appears at "point 4"; once the wrong value is found, the parameter information being stored in my exception is wrong. The other points clarify the subsequent confusion that arises. I have no idea how solve it.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Testapp
{
class Program
{
static void Main(string[] args)
{
string[] tmp1 = new string[2];
tmp1[0] = "val1";
tmp1[1] = "val2";
//please look at point 1
TestMethod1(tmp1);
//please look at point 2
TestMethod2(tmp1, "just a value");
}
private static void TestMethod1(string[] ArrayType)
{
try
{
throw new System.Exception("blow");
}
catch (System.Exception ex)
{
LogException(ex, ArrayType);
foreach (System.Collections.DictionaryEntry entry in ex.Data)
{
string tmp1 = entry.Key.ToString();
string tmp2 = entry.Value.ToString();
//point 1 (for param:ArrayType... well there is only 1 parameter)
//the value of tmp2 = val1
//and should be {val1,val2}
System.Diagnostics.Debugger.Break();
}
}
}
private static void TestMethod2(string[] ArrayType, string StringType)
{
try
{
throw new System.Exception("blow");
}
catch (System.Exception ex)
{
LogException(ex, ArrayType, StringType);
foreach (System.Collections.DictionaryEntry entry in ex.Data)
{
string tmp1 = entry.Key.ToString();
string tmp2 = entry.Value.ToString();
//point 2 (for param:ArrayType)
//the value of tmp2 = {val1,val2} (correct, this what i expected)
//please look at point 3
System.Diagnostics.Debugger.Break();
}
}
}
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
public static void LogException(System.Exception Exception, params object[] args)
{
using (CallerInfo callerinfo = new CallerInfo(1))
{
callerinfo.AddParameterInfo(Exception, args);
}
}
private class CallerInfo : IDisposable
{
private System.Reflection.ParameterInfo[] parameterinfos = null;
private string identifiername = string.Empty;
private string assemblyname = string.Empty;
public void AddParameterInfo(System.Exception Exception, params object[] sourceargs)
{
if (parameterinfos == null) return;
string locationname = identifiername + " - param:";
foreach (System.Reflection.ParameterInfo ParameterInfo in parameterinfos)
{
string KeyName = locationname + ParameterInfo.Name;
object parameter = null;
try
{
System.Diagnostics.Debugger.Break();
//point 4
//the next line goes wrong when there is ONLY 1 parameter on a method of type array
parameter = sourceargs[ParameterInfo.Position];
}
catch
{
parameter = null;
}
if (parameter == null)
{
if (!Exception.Data.Contains(KeyName))
{
Exception.Data.Add(KeyName, "*NULL*");
}
}
else
{
if (ParameterInfo.ParameterType.IsArray)
{
//point 3
//this is where i got confused
//the check if (ParameterInfo.ParameterType.IsArray) is returning true.. correct the first parameter in both methods are of type array
//however for TestMethod1 (that is having ONLY 1 parameter) the value of parameter (see point 4) is NOT an array anymore?????
//for TestMethod2 (that is having 2 parameters, but the SAME first parameter as passed in TestMethod1) the value of parameter (see point 4) is an array what is correct
System.Diagnostics.Debugger.Break();
if (parameter.GetType().IsArray)
{
string arrayvaluelist = "{";
try
{
System.Collections.ArrayList arraylist = new System.Collections.ArrayList((System.Collections.ICollection)parameter);
foreach (object arrayitem in arraylist)
{
if (arrayitem == null) { arrayvaluelist = arrayvaluelist + "*NULL*,"; continue; }
arrayvaluelist = arrayvaluelist + arrayitem.ToString() + ",";
}
arrayvaluelist = arrayvaluelist.Substring(0, arrayvaluelist.Length - 1);
arrayvaluelist = arrayvaluelist + "}";
}
catch
{
arrayvaluelist = "Error in constructing the arrayvalue list for parameter: " + ParameterInfo.Name;
}
if (!Exception.Data.Contains(KeyName))
{
Exception.Data.Add(KeyName, arrayvaluelist);
}
}
else
{
//point 5 -- i shouldn't be here !!!!
System.Diagnostics.Debugger.Break();
if (!Exception.Data.Contains(KeyName))
{
Exception.Data.Add(KeyName, parameter.ToString() + " warning wrong value is returned.");
}
}
}
else
{
if (!Exception.Data.Contains(KeyName))
{
Exception.Data.Add(KeyName, parameter.ToString());
}
}
}
}
}
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
public CallerInfo(int Level)
{
try
{
System.Diagnostics.StackTrace stackTrace = new System.Diagnostics.StackTrace();
System.Reflection.MethodBase methodbase = stackTrace.GetFrame(Level + 1).GetMethod();
parameterinfos = methodbase.GetParameters();
assemblyname = methodbase.ReflectedType.Assembly.ManifestModule.Name;
identifiername = methodbase.ReflectedType.FullName + "." + methodbase.Name;
}
catch
{
//broken
}
}
void IDisposable.Dispose()
{
parameterinfos = null;
}
}
}
}
Thank you very much for the good Minimal, Complete, and Verifiable code example. While the question as originally worded was not especially clear, having a good MCVE ensured that the exact issue can easily be understood. (It's unfortunate that three different people didn't bother to look at the most important part of the question…it seems the worst questions get up-voted, even while a not-entirely-clear question but which includes full code — the very most important part of any question — gets down-voted :( ).
Anyway, the issue here is your use of params in conjunction with the fact that the parameter itself is an array. It's important to understand what params actually means: the parameter declared in that way is in fact an array, and follows all the normal rules for regular array parameters. The only thing that params gives you is that you may optionally populate the array by providing multiple argument values, and the compiler will take those values and combine them into an array.
Where you got into trouble is that if you provide an array as the argument value, the compiler treats that as the actual array argument that was declared for the method and does not do any additional work.
The issue might have been more obvious if you'd been passing an object[] instead of a string[]. In that case, you can easily see that the whole object[] array matches exactly the parameter type for the LogException() method, and so is passed directly rather than being stored in another object[]. As it happens, arrays in C# are "covariant". In this case, the main thing that means is that if a method is expecting an object[] array, you can pass it an array of any type, because the elements of the passed array inherit the object type.
So when you pass the ArrayType value, the C# compiler recognizes this as compatible with the LogException() method's object[] parameter type, and just passes the array itself as that parameter, rather than storing it as a single element in an object[]. Then later when you go to retrieve the parameter values, it seems as though your LogException() method had been called by a method with two different parameters, i.e. two string values of "val1" and "val2", respectively.
So, how to fix this? Very easy: you just have to hide the array nature of the value from the C# compiler for the purpose of the call:
LogException(ex, (object)ArrayType);
I.e. in your TestMethod1() method, cast the ArrayType value to object when calling LogException(). This will force the compiler to treat the array object as a simple object value, preventing it from matching the value's type to the params object[] args parameter type, and store the value in a new object[] array for the call as you'd expected.

How to dynamically call a method in C#? [duplicate]

This question already has answers here:
Calling a function from a string in C#
(5 answers)
Closed 1 year ago.
I have a method:
add(int x,int y)
I also have:
int a = 5;
int b = 6;
string s = "add";
Is it possible to call add(a,b) using the string s?
how can i do this in c#?
Using reflection.
add has to be a member of some type, so (cutting out a lot of detail):
typeof(MyType).GetMethod("add").Invoke(null, new [] {arg1, arg2})
This assumes add is static (otherwise first argument to Invoke is the object) and I don't need extra parameters to uniquely identify the method in the GetMethod call.
Use reflection - try the Type.GetMethod Method
Something like
MethodInfo addMethod = this.GetType().GetMethod("add");
object result = addMethod.Invoke(this, new object[] { x, y } );
You lose strong typing and compile-time checking - invoke doesn't know how many parameters the method expects, and what their types are and what the actual type of the return value is. So things could fail at runtime if you don't get it right.
It's also slower.
If the functions are known at compile time and you just want to avoid writing a switch statement.
Setup:
Dictionary<string, Func<int, int, int>> functions =
new Dictionary<string, Func<int, int, int>>();
functions["add"] = this.add;
functions["subtract"] = this.subtract;
Called by:
string functionName = "add";
int x = 1;
int y = 2;
int z = functions[functionName](x, y);
You can use reflection.
using System;
using System.Reflection;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Program p = new Program();
Type t = p.GetType();
MethodInfo mi = t.GetMethod("add", BindingFlags.NonPublic | BindingFlags.Instance);
string result = mi.Invoke(p, new object[] {4, 5}).ToString();
Console.WriteLine("Result = " + result);
Console.ReadLine();
}
private int add(int x, int y)
{
return x + y;
}
}
}
#Richard's answer is great. Just to expand it a bit:
This can be useful in a situation where you dynamically created an object of unknown type and need to call its method:
var do = xs.Deserialize(new XmlTextReader(ms)); // example - XML deserialization
do.GetType().GetMethod("myMethodName").Invoke(do, new [] {arg1, arg2});
becasue at compile time do is just an Object.

Categories