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.
Related
I want to make a function that will return the sum ( concatenation for string ) of any number of argument I pass.
Below function works well with string but throwing error for other data types ( int, double...etc)
What am I missing?
Error:
Unhandled Exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException:
The call is ambiguous between the following methods or properties:
'System.Console.WriteLine(string, params object[])' and
'System.Console.WriteLine(char[])'
at CallSite.Target(Closure , CallSite , Type , Object )
Code:
public static dynamic sumfunction(params dynamic[] arr)
{
dynamic res=null;
for(int i=0;i<arr.Length;i++)
{
res += arr[i];
}
return res;
}
static void Main(string[] args)
{
dynamic vv=sumfunction("my","name");
Console.WriteLine(vv);
vv = sumfunction(5,6,7);
Console.WriteLine(vv);
}
Your current code is throwing an exception in Console.WriteLine(vv); because vv is null, and the call becomes ambiguous in the same way that this code does:
// error CS0121: The call is ambiguous between the following methods or properties:
// 'Console.WriteLine(char[])' and 'Console.WriteLine(string)'
Console.WriteLine(null);
So why is vv null in the second case? Because you started with null, and added to it. I suspect the binder is converting both null and the non-null integer to int? and then performing addition using the lifted addition operator. That reasoning is only an educated guess, but certainly the result is null. (You can check that with normal null checks on the result.)
The fix is to start with "the first element in the array" for the addition rather than with null, and only return null if the input array is either null or empty (or if a real addition ends up with null - which it could do if null is one of the elements in the array). You can also fix Console.WriteLine causing a problem even in that case by using object as the type of the local variable receiving the result, rather than dynamic. Here's an example with all that fixed, as well as using more idiomatic names:
using System;
using System.Linq;
public class Program
{
public static dynamic Sum(params dynamic[] arr)
{
if (arr == null || arr.Length == 0)
{
return null;
}
dynamic result = arr[0];
foreach (var item in arr.Skip(1))
{
result += item;
}
return result;
}
static void Main(string[] args)
{
object sum = Sum("my", "name");
Console.WriteLine(sum);
sum = Sum(5, 6, 7);
Console.WriteLine(sum);
Console.WriteLine(null);
}
}
Output:
myname
18
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.
This question already has answers here:
How does C# choose with ambiguity and params
(3 answers)
Closed 8 years ago.
How does c# make params work.
When you have code like
static string Concat(int arg)
{
return arg.ToString();
}
static string Concat(int arg, params string[] p)
{
string result = string.Empty;
result = result + arg.ToString();
foreach (var s in p)
{
result = result + s;
}
return result;
}
Resharper says the method with params hides the other method. Does this imply that under the hood The other method is being defined and calling the one with params passing in an empty array? Or something else entirely?
(please don't get bogged down on the crappy code - its just an example)
Edit to explain:
If i do not define the static string Concat(int arg) method i can still call the second as
var s = Concat(123);
despite that method not being defined .... so the question is: is the first method defined implicitly under the hood and calling the second with an empty array. Note i changed the arg type from string to int.
to explain the weirdness as i see it. When you call as above ... it hits the params object with an empty array, the param is not null - which is what i expected. When i first wrote that foreach loop i had a null check on params then discovered i didn't need it.
So ... you reason that it must be wrapping any params after the first parameter in an array right? A little bit of inlined code? ... so if you passed in "dave" you would get freddave as the output ....
However pass null into it ....
static void Main(string[] args)
{
Console.WriteLine(Concat("fred",null));
}
//static string Concat(string arg)
//{
// return arg;
//}
static string Concat(string arg, params int[] p)
{
string result = string.Empty;
result = result + arg;
if (p != null)
{
foreach (var s in p)
{
result = result + s.ToString();
}
}
return result;
}
and you discover in this case p is actually null ...
If you do that with the string example ... id of expected an array with 1 element of null ... instead the entire params object is null.
I think calling it "hiding" is misleading here, to be honest.
Both methods are definitely created, but if you call:
Concat(5);
then for that invocation both methods are applicable (in the terminology of the C# spec section 7.5.3.1) but the first method is better (section 7.5.3.2) because of this:
Otherwise, if MP is applicable in its normal form and MQ has a params array and is applicable only in its expanded form, then MP is better than MQ.
So in that case, the first method would be called.
The second method cannot be called using params expansion without specifying at least one string. It can still be called with an empty array, of course:
Concat(5, new string[0]);
I have some factory code that creates objects based on the value of a class member representing an enum:
public enum BeltPrinterType
{
None,
ZebraQL220,
ONiel
// add more as needed
}
public static BeltPrinterType printerChoice = BeltPrinterType.None;
public class BeltPrinterFactory : IBeltPrinterFactory
{
// http://stackoverflow.com/questions/17955040/how-can-i-return-none-as-a-default-case-from-a-factory?noredirect=1#comment26241733_17955040
public IBeltPrinter NewBeltPrinter()
{
switch (printerChoice)
{
case BeltPrinterType.ZebraQL220:
return new ZebraQL220Printer();
case BeltPrinterType.ONiel:
return new ONielPrinter();
default:
return new None();
}
}
}
I need to set the value of printerChoice before I call NewBeltPrinter() - that is, if it has been changed from its default "None" value. So I'm trying to assign that value based on the string representation, but have gotten to the proverbial point of no continuance with this attempt:
string currentPrinter = AppSettings.ReadSettingsVal("beltprinter");
Type type = typeof(PrintUtils.BeltPrinterType);
foreach (FieldInfo field in type.GetFields(BindingFlags.Static | BindingFlags.Public))
{
string display = field.GetValue(null).ToString();
if (currentPrinter == display)
{
//PrintUtils.printerChoice = (type)field.GetValue(null);
PrintUtils.printerChoice = ??? what now ???
break;
}
}
I've tried everything I could think of, and have been repaid with nothing but constant reprimands from the compiler, which has pretty much been dis[mis]sing me as a knave and a sorry rascal.
Does anybody know what I should replace the question marks with?
What about just this instead of your second code block:
PrintUtils.printerChoice = (PrintUtils.BeltPrinterType)
Enum.Parse(typeof(PrintUtils.BeltPrinterType),
AppSettings.ReadSettingsVal("beltprinter"));
Use Enum.Parse to convert a string into the enum value.
(or Enum.TryParse to attempt to do it without raising an exception if it didn't parse)
edit
If you don't have an Enum.Parse available, then you will have to do the conversion yourself:
switch (stringValue)
{
case "BeltPrinterType.ONiel": enumValue = BeltPrinterType.ONiel; break;
...etc...
}
I can't compile to .NET 1.1 but this seems to work in 2.0
PrintUtils.printerChoice = (BeltPrinterType)field.GetValue(null);
EDIT : I just realized this was basically a comment in your code... But yeah I really don't see why this wouldn't work even in 1.1
From the Smart Device Framework 1.x code base:
public static object Parse(System.Type enumType, string value, bool ignoreCase)
{
//throw an exception on null value
if(value.TrimEnd(' ')=="")
{
throw new ArgumentException("value is either an empty string (\"\") or only contains white space.");
}
else
{
//type must be a derivative of enum
if(enumType.BaseType==Type.GetType("System.Enum"))
{
//remove all spaces
string[] memberNames = value.Replace(" ","").Split(',');
//collect the results
//we are cheating and using a long regardless of the underlying type of the enum
//this is so we can use ordinary operators to add up each value
//I suspect there is a more efficient way of doing this - I will update the code if there is
long returnVal = 0;
//for each of the members, add numerical value to returnVal
foreach(string thisMember in memberNames)
{
//skip this string segment if blank
if(thisMember!="")
{
try
{
if(ignoreCase)
{
returnVal += (long)Convert.ChangeType(enumType.GetField(thisMember, BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase).GetValue(null),returnVal.GetType(), null);
}
else
{
returnVal += (long)Convert.ChangeType(enumType.GetField(thisMember, BindingFlags.Public | BindingFlags.Static).GetValue(null),returnVal.GetType(), null);
}
}
catch
{
try
{
//try getting the numeric value supplied and converting it
returnVal += (long)Convert.ChangeType(System.Enum.ToObject(enumType, Convert.ChangeType(thisMember, System.Enum.GetUnderlyingType(enumType), null)),typeof(long),null);
}
catch
{
throw new ArgumentException("value is a name, but not one of the named constants defined for the enumeration.");
}
//
}
}
}
//return the total converted back to the correct enum type
return System.Enum.ToObject(enumType, returnVal);
}
else
{
//the type supplied does not derive from enum
throw new ArgumentException("enumType parameter is not an System.Enum");
}
}
}
public static void fillCheckList(string ListType,int RecordNum,CheckBox chkRequired,TextBox txtComplete,TextBox txtMemo)
{
string sql_Check = String.Format(#"SELECT l.Required,l.Completed,l.ISP,l.Memo_Notes,
t.List_Desc
FROM List_Data l, List_Type t
WHERE l.List_ID = t.List_ID
AND l.Record_Num = {0}
AND t.List_Desc = '{1}'", RecordNum,ListType);
ListData LIST = new ListData();
SqlConnection sqlConn = null;
SqlCommand cmd_Check;
SqlDataReader dr_Check;
try
{
sqlConn = new SqlConnection(databaseConnectionString);
sqlConn.Open();
cmd_Check = new SqlCommand(sql_Check, sqlConn);
dr_Check = cmd_Check.ExecuteReader();
while (dr_Check.Read())
{
LIST = new ListData(Convert.ToBoolean(dr_Check["Required"]), dr_Check["Completed"].IsNull() ? (DateTime?)null : Convert.ToDateTime(dr_Check["Completed"]), dr_Check["Memo_Notes"].ToString());
}
chkRequired.Checked = LIST.REQUIRED;
txtComplete.Text = LIST.COMPLETED.HasValue ? LIST.COMPLETED.Value.ToShortDateString() : "";
txtMemo.Text = LIST.MEMO_NOTES;
}
catch (Exception e)
{
MessageBox.Show("Error found in fillCheckList..." + Environment.NewLine + e.ToString());
}
finally
{
if (sqlConn != null)
{
sqlConn.Close();
}
}
}
As you can see, i take in an integer variable for the project id, a string variable for list type, and 2 text box type.
i am thus using this method to accept 4 arguments.. what i want to do is that i also want it to accept 5 arguments . that is.. include one more text box..
so that it either takes 4 arguments or 5 arguments accordingly/
how do i do that in the same method.
Do not use params for this.
Use params to represent the idea "I can take zero, one, or arbitrarily many extra parameters. It sounds like you want to take zero or one extra parameters.
TJMonk15 is right; you should either use an optional parameter (in C# 4), or write two methods and have one of them call the other with a default value for the extra parameter. Preferably the latter.
(And for goodness sake fix that SQL injection vuln!)
If you are using c# 4.0 you can use Optional Arguments. If not, you should use method overloading and using one overload to call the other with a default value for the last parameter.
params is your friend.
eg:
public static void fillCheckList(string ListType,int RecordNum,CheckBox chkRequired, params TextBox[] txtBoxes)
{
TextBox txtComplete = null;
TextBox txtMemo = null;
TextBox txtThirdOne = null;
if(txtBoxes.Length < 1)
{
throw new Exception("At least the txtComplete-Textbox has to be given");
}
else
{
txtComplete = txtBoxes[0];
if(txtBoxes.Length >= 2)
txtMemo = txtBoxes[1];
if(txtBoxes.Length >= 3)
txtThirdOne = txtBoxes[2];
}
// do stuff
}
Check the keyword params
You can use params but it requires either you to have a random number of arguments of the same type or having a loose typed collection of arguments (object)
You can also write your function with 5 arguments and provide an overloaded method with 4 arguments that calls the former and defaults the last parameter.
public static void fillCheckList(string ListType, int RecordNum, CheckBox chkRequired, TextBox txtComplete)
{
fillCheckList(ListType, RecordNum, chkRequired, txtComplete, null);
}
You have 3 options
1.
public static void fillCheckList(string ListType, int RecordNum, CheckBox chkRequired, TextBox txtComplete, TextBox txtMemo) {
fillCheckList(ListType, RecordNum, chkRequired, txtComplete, txtMemo, null);
}
public static void fillCheckList(string ListType, int RecordNum, CheckBox chkRequired, TextBox txtComplete,TextBox txtMemo,TextBox txtMemo, TextBox txtMemo) {
// implementation
}
public static void fillCheckList(string ListType, int RecordNum, CheckBox chkRequired, IEnumerable textBoxes) {
// implementation
}
3.
public static void fillCheckList(string ListType, int RecordNum, CheckBox chkRequired, params TextBox[] textBoxes) {
// implementation
}
Option 1. is ok if you need to add different members and you have a finite set of possibilities.
Option 2. is ok. But not a elegant as 3.
Option 3. is maybe the best thing to do, as long as the added parameters have a common ancestor - object if it must be. Remember that params alway has to be the last parameter.