How to create optional parameters using object array - c#

I am creating a method which MUST be use with or without parameters.
I am using object array so that I can hold string, integer, binary types, etc at the same time.
Method:
SQLDB_UsingReader(string strSQL_WithParam, params object[,] obj)
Error:
The array must be a single dimensional array.
P.S
This method SQLDB_UsingReader(string strSQL_WithParam, object[,] obj) is working, but when I add "params" as the solution I searched when creating optional parameter, the error occurs.
CODE
public void SQLDB_UsingReader(string strSQL_WithParam, params object[,] obj)
{
try
{
using (SqlCommand mCmd = new SqlCommand(strSQL_WithParam, mConn))
{
for (int i = 0; i < obj.Length / 2; i++)
{
if (obj[i, 1] == null || obj[i, 1].ToString() == "" || obj[i, 1].ToString().Length == 0)
{ mCmd.Parameters.Add(new SqlParameter(obj[i, 0].ToString(), DBNull.Value)); }
else
{ mCmd.Parameters.Add(new SqlParameter(obj[i, 0].ToString(), obj[i, 1])); }
}
mConn.Open();
mDataReader = mCmd.ExecuteReader();
mConn.Close();
}
}
catch (Exception ex) { ex.ToString(); }
}

Use Dictionary<string, object>. You have two values: a string and an object per parameter you wish to set. Either create a class that has one of each and use params for an array of that class or simply use the built in type for a collection of named values.
public void SQLDB_UsingReader(string strSQL_WithParam, IDictionary<string, object> obj)
{
try
{
string where = obj.Any() ? ("where " + string.Join("AND", obj
.Select(x => x.Key +"==#" + x.Key)) : "";
using (SqlCommand mCmd = new SqlCommand(strSQL_WithParam + where, mConn))
{
foreach pair in obj
{
... (use pair.Value and pair.Key)
}
...
}
}
...
}

Related

Getting expression text

I want to pass the name of a property of a model to a method. Instead of using the name as string, I am using lambda expression as it is easy to make a typo, and also property names may be changed. Now if the property is a simple property (e.g: model.Name) I can get the name from the expression. But if it is a nested property (e.g: model.AnotherModel.Name) then how can I get full text ("AnotherModel.Name") from the expression. For example, I have the following classes:
public class BaseModel
{
public ChildModel Child { get; set; }
public List<ChildModel> ChildList { get; set; }
public BaseModel()
{
Child = new ChildModel();
ChildList = new List<ChildModel>();
}
}
public class ChildModel
{
public string Name { get;set; }
}
public void GetExpressionText<T>(Expression<Func<T, object>> expression)
{
string expText;
//what to do??
return expText;
}
GetExpressionText<BaseModel>(b => b.Child); //should return "Child"
GetExpressionText<BaseModel>(b => b.Child.Name); //should return "Child.Name"
GetExpressionText<BaseModel>(b => b.ChildList[0].Name); //should return "ChildList[0].Name"
My first thought was to use expression.Body.ToString() and tweak that a bit, but you would still need to deal with Unary (convert) etc. Assuming this is for logging and you want more control, the below can be used for formatting as wanted (e.g. if you want Child->Name for display purposes, string.Join("->",..) can be used). It may not be complete, but should you find any unsupported types, they should be easy to add.
PS: this post was generated before the question was closed. Just noticed it was reopend and submitting it now, but I haven't checked if particulars have been changed.
public string GetName(Expression e, out Expression parent)
{
if(e is MemberExpression m){ //property or field
parent = m.Expression;
return m.Member.Name;
}
else if(e is MethodCallExpression mc){
string args = string.Join(",", mc.Arguments.SelectMany(GetExpressionParts));
if(mc.Method.IsSpecialName){ //for indexers, not sure this is a safe check...
return $"{GetName(mc.Object, out parent)}[{args}]";
}
else{ //other method calls
parent = mc.Object;
return $"{mc.Method.Name}({args})";
}
}
else if(e is ConstantExpression c){ //constant value
parent = null;
return c.Value?.ToString() ?? "null";
}
else if(e is UnaryExpression u){ //convert
parent= u.Operand;
return null;
}
else{
parent =null;
return e.ToString();
}
}
public IEnumerable<string> GetExpressionParts(Expression e){
var list = new List<string>();
while(e!=null && !(e is ParameterExpression)){
var name = GetName(e,out e);
if(name!=null)list.Add(name);
}
list.Reverse();
return list;
}
public string GetExpressionText<T>(Expression<Func<T, object>> expression) => string.Join(".", GetExpressionParts(expression.Body));
You could use the C# 6.0 feature: nameof(b.Child) "Used to obtain the simple (unqualified) string name of a variable, type, or member."
which will also change on renaming. But this will only return the propertyname and not the complete path. Returning a complete path will be difficult, because only one instance is passed.
Closest i know right now is by simply using expression.Body.ToString() which would result in b.ChildList.get_Item(0).Name as a result.
You would still have to remove the first b. from the string if not wanted, and you could go even further to your intended output with Regex by replacing the get_Item(0) with the typical Index-Accessor.
(Also i had to make the ChildList and the Name-Property of ChildModel public to get it to work)
This Should get you most of the way there:
public static string GetFullPath<T>(Expression<Func<T>> action)
{
var removeBodyPath = new Regex(#"value\((.*)\).");
var result = action.Body.ToString();
var replaced = removeBodyPath.Replace(result, String.Empty);
var seperatedFiltered = replaced.Split('.').Skip(1).ToArray();
return string.Join(".", seperatedFiltered);
}
It gets ugly quite quickly...
public static string GetExpressionText<T>(Expression<Func<T, object>> expression)
{
bool needDot = false;
Expression exp = expression.Body;
string descr = string.Empty;
while (exp != null)
{
if (exp.NodeType == ExpressionType.MemberAccess)
{
// Property or field
var ma = (MemberExpression)exp;
descr = ma.Member.Name + (needDot ? "." : string.Empty) + descr;
exp = ma.Expression;
needDot = true;
}
else if (exp.NodeType == ExpressionType.ArrayIndex)
{
// Array indexer
var be = (BinaryExpression)exp;
descr = GetParameters(new ReadOnlyCollection<Expression>(new[] { be.Right })) + (needDot ? "." : string.Empty) + descr;
exp = be.Left;
needDot = false;
}
else if (exp.NodeType == ExpressionType.Index)
{
// Object indexer (not used by C#. See ExpressionType.Call)
var ie = (IndexExpression)exp;
descr = GetParameters(ie.Arguments) + (needDot ? "." : string.Empty) + descr;
exp = ie.Object;
needDot = false;
}
else if (exp.NodeType == ExpressionType.Parameter)
{
break;
}
else if (exp.NodeType == ExpressionType.Call)
{
var ca = (MethodCallExpression)exp;
if (ca.Method.IsSpecialName)
{
// Object indexer
bool isIndexer = ca.Method.DeclaringType.GetDefaultMembers().OfType<PropertyInfo>().Where(x => x.GetGetMethod() == ca.Method).Any();
if (!isIndexer)
{
throw new Exception();
}
}
else if (ca.Object.Type.IsArray && ca.Method.Name == "Get")
{
// Multidimensiona array indexer
}
else
{
throw new Exception();
}
descr = GetParameters(ca.Arguments) + (needDot ? "." : string.Empty) + descr;
exp = ca.Object;
needDot = false;
}
}
return descr;
}
private static string GetParameters(ReadOnlyCollection<Expression> exps)
{
var values = new string[exps.Count];
for (int i = 0; i < exps.Count; i++)
{
if (exps[i].NodeType != ExpressionType.Constant)
{
throw new Exception();
}
var ce = (ConstantExpression)exps[i];
// Quite wrong here... We should escape string values (\n written as \n and so on)
values[i] = ce.Value == null ? "null" :
ce.Type == typeof(string) ? "\"" + ce.Value + "\"" :
ce.Type == typeof(char) ? "'" + ce.Value + "\'" :
ce.Value.ToString();
}
return "[" + string.Join(", ", values) + "]";
}
The code is quite easy to read, but it is quite long... There are 4 main cases: MemberAccess, that is accessing a property/field, ArrayIndex that is using the indexer of a single-dimensional array, Index that is unused by the C# compiler, but that should be using the indexer of an object (like the [...] of the List<> you are using), and Call that is used by C# for using an indexer or for accessing multi-dimensional arrays (new int[5, 4]) (and for other method calls, but we disregard them).
I support multidimensional arrays, jagged array s(arrays of arrays, new int[5][]) or arrays of indexable objects (new List<int>[5]) or indexable objects of indexable objects (new List<List<int>>). There is even support for multi-property indexers (indexers that use more than one key value, like obj[1, 2]). Small problem: printing the "value" of the indexers: I support only null, integers of various types, chars and strings (but I don't escape them... ugly... if there is a \n then it won't be printed as \n). Other types are not really supported... They will print what they will print (see GetParameters() if you want)

call a generic method with an object of an unknown type

I have this method
public string DictionaryToString<T, U>(Dictionary<T, U> dict)
{
var valueStrings = dict.Select(x => x.Key.ToString() + ": " + x.Value.ToString());
return String.Join("\n", valueStrings);
}
And I have this object that I want to pass into it
if ((value !=null) && value.GetType().IsGenericType &&
value.GetType().GetGenericTypeDefinition() == typeof (Dictionary<,>))
{
var castValue = value as Dictionary<,>; // this cast does not work
return DictionaryToString(castValue);
}
else
{
return value.ToString();
}
I can use reflection code like this in .Net 4.5
var targetMethodInfo = typeof(MyType).GetMethod("DictionaryToString");
var valueTypeArgs = value.GetType().GenericTypeArguments;
var genericMethod = targetMethodInfo.MakeGenericMethod(valueTypeArgs);
var result = genericMethod.Invoke(this, new[] {value });
return result.ToString();
ButType.GenericTypeArguments is new in .Net 4.5. So how can I do that cast in .Net 4.0?
You are only calling ToString on the key and value, so simply have this method take an IDictionary (non-generic), you are not using anything in there that is type-specific to T or U.
You can then simply cast all arguments to IDictionary:
var d = arg as IDictionary;
if (d != null)
{
var res = DictionaryToString(d);
}
You may also need to amend the DictionaryToString implementation:
static string DictionaryToString(IDictionary d)
{
var vals = new List<string>();
foreach (DictionaryEntry de in d)
{
vals.Add(de.Key.ToString() + ": " + de.Value.ToString());
}
return String.Join("\n", vals);
}
Alternatively, if you really want to use LINQ, you could try casting to dynamic (it isn't possible to cast to anything else as this could be a generic dictionary (KeyValuePair<>) or non-generic hashtable (DictionaryEntry)):
var valueStrings = d.Cast<dynamic>().Select(de => de.Key.ToString() + ": " + de.Value.ToString());
return string.Join("\n", valueStrings);
This basically "duck types" the existence of the Key and Value properties.
This sounds like it might be a case for the old System.Collections namespace:
private static string DictionaryToString(IDictionary dict) {
if (null == dict) throw new ArgumentNullException("dict");
var valueStrings = new List<string>();
foreach (DictionaryEntry item in dict) {
valueStrings.Add(item.Key + ": " + item.Value);
}
return string.Join("\n", valueStrings.ToArray());
}
private static string Test(object value) {
var dict = value as IDictionary;
if (dict != null) {
return DictionaryToString(dict);
}
if (value == null) {
return null;
}
return value.ToString();
}
private static void Main(string[] args) {
var aDictionary = new Dictionary<int, string> {
{ 1, "one" },
{ 2, "two" },
{ 3, "three" }
};
Console.WriteLine(Test(aDictionary));
var anotherDictionary = new Dictionary<string, object> {
{ "one", 1 },
{ "two", "2" },
{ "three", new object() }
};
Console.WriteLine(Test(anotherDictionary));
Console.ReadLine();
}
Reasoning:
The non-generic IDictionary will be a collection of key-value pairs in which the key is an object and the value is an object. All instances of object support ToString, so all keys and values of the collection can be converted to a string without knowing their specific types.
The reason why this does not work:
var castValue = value as Dictionary<,>
is because the generic type Dictionary<TKey, TValue> requires 2 type arguments. Without those type arguments, the collection is not generic. You'd be better off using the non-generic IDictionary if you do not know the key or value types at compile-time.
GetGenericTypeDefinition is available for previous versions http://msdn.microsoft.com/en-us/library/system.type.getgenerictypedefinition(v=vs.100).aspx. I think the 4.5 page on MSDN is missing the "Other versions" dropdown.
You could cast to dynamic in .NET 4.0. The prior typeof(Dictionary<,>) check will ensure that you won't get runtime errors.
var castValue = value as dynamic;
return DictionaryToString(castValue);
Why not?
if ((value !=null) && value.GetType().IsGenericType &&
value.GetType().GetGenericTypeDefinition() == typeof (Dictionary<,>))
{
return DictionaryToString(castValue);
}
else
{
return value.ToString();
}

How do i create the functions of List<float> in the options file?

I have in the options file two functions GetKey and SetKey.
I set a key then in the settings_file.txt it will look like:
text = hello where text is the key then = and hello is the value for the current key.
Now i need to add another two functions the first one is type of List that get a string and return a List
And a another function that get a Key and a List.
So this is the first two functions allready working GetKey and SetKey:
/*----------------------------------------------------------------
* Module Name : OptionsFile
* Description : Saves and retrievs application options
* Author : Danny
* Date : 10/02/2010
* Revision : 1.00
* --------------------------------------------------------------*/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.IO;
using System.Configuration;
/*
* Introduction :
*
* This module helps in saving application options
*
*
* Typical file could look like this:
* user_color=Red
* time_left=30
*
*
*
*
*
* */
namespace DannyGeneral
{
class OptionsFile
{
/*----------------------------------------
* P R I V A T E V A R I A B L E S
* ---------------------------------------*/
/*---------------------------------
* P U B L I C M E T H O D S
* -------------------------------*/
string path_exe;
string temp_settings_file;
string temp_settings_dir;
string Options_File;
StreamWriter sw;
StreamReader sr;
/*----------------------------------------------------------
* Function : OptionsFile
* Description : Constructor
* Parameters : file_name is the name of the file to use
* Return : none
* --------------------------------------------------------*/
public OptionsFile(string settings)
{
if (!File.Exists(settings))
{
if (!Directory.Exists(Path.GetDirectoryName(settings)))
{
Directory.CreateDirectory(Path.GetDirectoryName(settings));
}
File.Create(settings).Close();
}
path_exe = Path.GetDirectoryName(Application.LocalUserAppDataPath);
Options_File = settings;
}
/*----------------------------------------------------------
* Function : GetKey
* Description : gets the value of the key.
* Parameters : key
* Return : value of the key if key exist, null if not exist
* --------------------------------------------------------*/
public string GetKey(string key)
{
// string value_of_each_key;
string key_of_each_line;
string line;
int index;
string key_value;
key_value = null;
sr = new StreamReader(Options_File);
while (null != (line = sr.ReadLine()))
{
index = line.IndexOf("=");
// value_of_each_key = line.Substring(index+1);
if (index >= 1)
{
key_of_each_line = line.Substring(0, index);
if (key_of_each_line == key)
{
key_value = line.Substring(key.Length + 1);
}
}
else
{
}
}
sr.Close();
return key_value;
}
/*----------------------------------------------------------
* Function : SetKey
* Description : sets a value to the specified key
* Parameters : key and a value
* Return : none
* --------------------------------------------------------*/
public void SetKey(string key , string value)
{
bool key_was_found_inside_the_loop;
string value_of_each_key;
string key_of_each_line ;
string line;
int index;
key_was_found_inside_the_loop = false;
temp_settings_file = "\\temp_settings_file.txt";
temp_settings_dir = path_exe + #"\temp_settings";
if (!Directory.Exists(temp_settings_dir))
{
Directory.CreateDirectory(temp_settings_dir);
}
sw = new StreamWriter(temp_settings_dir+temp_settings_file);
sr = new StreamReader(Options_File);
while (null != (line = sr.ReadLine()))
{
index = line.IndexOf("=");
key_of_each_line = line.Substring(0, index);
value_of_each_key = line.Substring( index + 1);
// key_value = line.Substring(0,value.Length);
if (key_of_each_line == key)
{
sw.WriteLine(key + " = " + value);
key_was_found_inside_the_loop = true;
}
else
{
sw.WriteLine(key_of_each_line+"="+value_of_each_key);
}
}
if (!key_was_found_inside_the_loop)
{
sw.WriteLine(key + "=" + value);
}
sr.Close();
sw.Close();
File.Delete(Options_File);
File.Move(temp_settings_dir + temp_settings_file, Options_File);
return;
}
After this two functions i did:
public List<float> GetListFloatKey(string keys)
{
int j;
List<float> t;
t = new List<float>();
int i;
for (i = 0; ; i++)
{
j = Convert.ToInt32(GetKey((keys + i).ToString()));
if (j == 0)
{
break;
}
else
{
t.Add(j);
}
}
if (t.Count == 0)
return null;
else
return t;
}
public void SetListFloatKey(string key, List<float> Values)
{
int i;
for (i = 0; i < Values.Count; i++)
{
string indexed_key;
indexed_key = string.Format("{0}{1}", key, i);
// indexed_key = Key + i.ToString();
SetKey(indexed_key, Values[i].ToString());
}
}
But they are not good.
The last one the SetListFloatKey when i put a List in it the result in the text file settings_file.txt is for exmaple:
coordinates01 = 123
coordinates02 = 144
coordinates03 = 145
For every cell/index in the List i get its making a key. What i need is that the List i get will have one key the format in the text file should be like this:
coordinates = 123,144,145......and so on one key and then all the values from the List i get.
Then in the GetListFloatKey i need re format the values according to the key for example coordinates and return a List with the values in index 0 123 in 1 144 in 2 145 and so on....
The qustion if the function the way im doing them are good in the way im using in both GetKey and SetKey ? And how do i format and re format the values ?
At the moment you are calling SetKey within SetListFloatKey for every item in the list. Instead, you need to build a string and call it once, along the lines of (basic testing done):
public static void SetListFloatKey(string key, List<float> Values)
{
StringBuilder sb = new StringBuilder();
foreach (float value in Values)
{
sb.AppendFormat("{0},", value);
}
SetKey(key, sb.ToString());
}
Note I am getting lazy here - the last item will have a comma after it. Then when loading the list:
public static List<float> GetListFloatKey(string keys)
{
List<float> result = new List<float>();
string s = GetKey(keys);
string[] items = s.Split(new char[] { ',' });
float f;
foreach (string item in items)
{
if (float.TryParse(item, out f))
result.Add(f);
}
return result;
}
However, given you are reading and writing an options file, you might want to investigate options around serializing your objects to and from files.
EDIT There are a few ways you can get rid of the extra comma. One way is to not put it in in the first place...
string sep = "";
foreach (float value in Values)
{
sb.AppendFormat("{0}{1}", sep, value);
if (sep == "") sep = ",";
}
...and another is to exclude it in the call to SetKey...
foreach (float value in Values)
{
sb.AppendFormat(",{0}", value);
}
SetKey(key, sb.ToString().Substring(1));
..note that in both of these cases I moved the comma to the start to make life easier. Alternatively, you could store the numbers in an array and use Array.Join.
I think that you are wasting too much time thinking about how to format the file each time you make a change, you are also causing a lot of file overhead each time you check for a key.
Consider using a class like
public class Options
{
public static string FILENAME = #"C:\Test\testfile.txt";
public List<KeyValuePair<string, string>> OrderedKeys { get; set; }
public Dictionary<string, KeyValuePair<string, string>> Pairs { get; set; }
public string GetKey(string key)
{
return this.Pairs[key].Value;
}
public void SetKey(string key, string value)
{
if(this.Pairs.ContainsKey(key))
{
KeyValuePair<string, string> pair = new KeyValuePair<string, string>(key, value);
this.OrderedKeys.Insert(this.OrderedKeys.IndexOf(this.Pairs[key]), pair);
this.Pairs[key] = pair;
}
}
public Options()
{
LoadFile();
}
~Options()
{
WriteFile();
}
private void LoadFile()
{
Regex regex = new Regex(#"(?<key>\S*?)\s*=\s*(?<val>\S*?)\s*\r\n");
MatchCollection matches = regex.Matches(File.ReadAllText(FILENAME));
this.OrderedKeys = new List<KeyValuePair<string, string>>();
this.Pairs = new Dictionary<string, KeyValuePair<string, string>>();
foreach (Match match in matches)
{
KeyValuePair<string, string> pair =
new KeyValuePair<string,string>(match.Groups["key"].Value, match.Groups["val"].Value);
this.OrderedKeys.Add(pair);
this.Pairs.Add(pair.Key, pair);
}
}
private void WriteFile()
{
if (File.Exists(FILENAME))
File.Delete(FILENAME);
using (System.IO.StreamWriter file = new System.IO.StreamWriter(FILENAME))
{
foreach (KeyValuePair<string, string> pair in this.OrderedKeys)
{
file.WriteLine(pair.Key + " = " + pair.Value);
}
}
}
}
Notice that the options object will read from the file once, and writeout when it is destroyed, meanwhile it will hold a local dictionary of the values in your file. You can then GetKey() and SetKey() to get and set your options.
I modified my original post to use a list and a dictionary, this is because a Dictionary on its own does not maintain the original order that pairs are added, so the list ensures that the options are always written to the file in the correct order.
You will also notice I threw in a Regular Expression to parse your file, makes things much easier and quicker and allows for things like extra whitespace in the options file.
Once you have done this it is easy to add functions like
public List<float> GetListFloatKey(string keybase)
{
List<float> ret = new List<float>();
foreach (string key in this.Pairs.Keys)
{
if (Regex.IsMatch(key, keybase + "[0-9]+"))
ret.Add(float.Parse(this.Pairs[key].Value));
}
return ret;
}
public void SetListFloatKey(string keybase, List<float> values)
{
List<string> oldkeys = new List<string>();
int startindex = -1;
foreach (string key in this.Pairs.Keys)
{
if (Regex.IsMatch(key, keybase + "[0-9]+"))
{
if (startindex == -1)
startindex = this.OrderedKeys.IndexOf(this.Pairs[key]);
oldkeys.Add(key);
}
}
foreach (string key in oldkeys)
{
this.OrderedKeys.Remove(this.Pairs[key]);
this.Pairs.Remove(key);
}
for (int i = 0; i < values.Count; i++)
{
KeyValuePair<string, string> pair = new KeyValuePair<string, string>(keybase + i.ToString(), values[i].ToString());
if (startindex != -1)
this.OrderedKeys.Insert(startindex + i, pair);
else
this.OrderedKeys.Add(pair);
this.Pairs.Add(pair.Key, pair);
}
}
It is easier to do that at this point because you have abstracted the actual file structure away and are now just dealing with a Dictionary

Handle null parameters while calling a method using Reflection

I'm trying to write code that will infer types from a parameter list and then call the method that matches those parameters. This works very well, except when the parameter list has a null value in it.
I am wondering how I might cause the Type.GetMethod call to match a function/overload, even with a null parameter in the parameters list.
object CallMethodReflection(object o, string nameMethod, params object[] args)
{
try
{
var types = TypesFromObjects(args);
var theMethod = o.GetType().GetMethod(nameMethod, types);
return (theMethod == null) ? null : theMethod.Invoke(o, args);
}
catch (Exception ex)
{
return null;
}
}
Type[] TypesFromObjects(params object[] pParams)
{
var types = new List<Type>();
foreach (var param in pParams)
{
types.Add((param == null) ? null : param.GetType());
}
return types.ToArray();
}
The main problem line is the types.Add((param == null) ? null : param.GetType());, which will cause the GetMethod call to fail with a null value in the types array.
void Function1(string arg1){ }
void Function1(string arg1, string arg2){ }
void Function1(string arg1, string arg2, string arg3){ }
void Function2(string arg1){ }
void Function2(string arg1, int arg2){ }
void Function2(string arg1, string arg2){ }
/*1*/ CallMethodReflection(obj, "Function1", "String", "String"); // This works
/*2*/ CallMethodReflection(obj, "Function1", "String", null); // This doesn't work, but still only matches one overload
/*3*/ CallMethodReflection(obj, "Function2", "String", "String"); // This works
/*4*/ CallMethodReflection(obj, "Function2", "String", null); // This doesn't work, and I can see why this would cause problems
Mainly, I'm trying to determine how to change my code so that line /*2*/ works as well.
There are overrides to the GetMethod call which take an object derived from the Binder class. This allows you to override the default method binding and return the method you want to use, based on the actual parameters passed. This is essentially what the two other answers are doing as well. There is some sample code here:
http://msdn.microsoft.com/en-us/library/system.reflection.binder.aspx
An option that has not been mentioned is to use Fasterflect, a library designed to make reflection tasks easier and faster (through IL generation).
To invoke a method given a dictionary of named parameters (or an object with properties that should be used as parameters), you can invoke the best match like this:
obj.TryCallMethod( "SomeMethod", argsDictionary );
obj.TryCallMethod( "AnotherMethod", new { Foo = "Bar" } );
If all you have are the parameter values and their ordering, you can use another overload:
obj.TryCallMethodWithValues( "MyMethod", 42, "foo", "bar", null, 2.0 );
PS: You'll need to obtain the latest bits from source control to take advantage of the TryCallMethodWithValues extension.
Disclaimer: I am a contributor to the Fasterflect project.
For any parameter that is null you could just match to any reference type. The following very simple/naive code will work for your methods as shown, but it doesn't handle things like exceptions on ambiguities or more complex cases using ref/out parameters or being able to pass a derived type to the method or generic methods.
If you are using 4.0 then simply using dynamic might be a better choice.
object CallMethodReflection(object o, string nameMethod, params object[] args)
{
try
{
var types = TypesFromObjects(args);
var oType = o.GetType();
MethodInfo theMethod = null;
// If any types are null have to perform custom resolution logic
if (types.Any(type => type == null))
{
foreach (var method in oType.GetMethods().Where(method => method.Name == nameMethod))
{
var parameters = method.GetParameters();
if (parameters.Length != types.Length)
continue;
//check to see if all the parameters match close enough to use
bool methodMatches = true;
for (int paramIndex = 0; paramIndex < parameters.Length; paramIndex++)
{
//if arg is null, then match on any non value type
if (args[paramIndex] == null)
{
if (parameters[paramIndex].ParameterType.IsValueType)
{
methodMatches = false;
break;
}
}
else //otherwise match on exact type, !!! this wont handle things passing a type derived from the parameter type !!!
{
if (parameters[paramIndex].ParameterType != args[paramIndex].GetType())
{
methodMatches = false;
break;
}
}
}
if (methodMatches)
{
theMethod = method;
break;
}
}
}
else
{
theMethod = oType.GetMethod(nameMethod, types);
}
Console.WriteLine("Calling {0}", theMethod);
return theMethod.Invoke(o, args);
}
catch (Exception ex)
{
Console.WriteLine("Could not call method: {0}, error: {1}", nameMethod, ex.ToString());
return null;
}
}
I think you would have to do:
var methods = o.GetType().GetMethods().Where(m => m.Name == methodName);
Then essentially do your own overload resolution. You could try your existing method first, catch the exception and then try the above.
Thanks to the MSDN link as well as some additional SO discussion and an outside forum discussion involving a prominent SO member, I have tried to implement my own solution, which is working for me so far.
I created a class which inherited the Binder class and put my logic to handle the potentially null arguments/types in there.
object CallMethodReflection(object o, string nameMethod, params object[] args)
{
try
{
var types = TypesFromObjects(args);
var theMethod = o.GetType().GetMethod(nameMethod, CustomBinder.Flags, new CustomBinder(), types, null);
return (theMethod == null) ? null : theMethod.Invoke(o, args);
}
catch (Exception ex)
{
return null;
}
}
Type[] TypesFromObjects(params object[] pParams)
{
var types = new List<Type>();
foreach (var param in pParams)
{
types.Add((param == null) ? typeof(void) : param.GetType()); // GetMethod above doesn't like a simply null value for the type
}
return types.ToArray();
}
private class CustomBinder : Binder
{
public const BindingFlags Flags = BindingFlags.Public | BindingFlags.Instance;
public override MethodBase SelectMethod(BindingFlags bindingAttr, MethodBase[] matches, Type[] types, ParameterModifier[] modifiers)
{
if (matches == null)
throw new ArgumentNullException("matches");
foreach (var match in matches)
{
if (MethodMatches(match.GetParameters(), types, modifiers))
return match;
}
return Type.DefaultBinder.SelectMethod(bindingAttr, matches, types, modifiers); // No matches. Fall back to default
}
private static bool MethodMatches(ParameterInfo[] parameters, Type[] types, ParameterModifier[] modifiers)
{
if (types.Length != parameters.Length)
return false;
for (int i = types.Length - 1; i >= 0; i--)
{
if ((types[i] == null) || (types[i] == typeof(void)))
{
if (parameters[i].ParameterType.IsValueType)
return false; // We don't want to chance it with a wonky value
}
else if (!parameters[i].ParameterType.IsAssignableFrom(types[i]))
{
return false; // If any parameter doesn't match, then the method doesn't match
}
}
return true;
}
}
Since the Binder class is an abstract class, you have to override a few other members to actually use this code, but most of my overrides just front the Type.DefaultBinder object.
public override FieldInfo BindToField(BindingFlags bindingAttr, FieldInfo[] matches, object value, CultureInfo culture)
{
return Type.DefaultBinder.BindToField(bindingAttr, matches, value, culture);
}
I didn't test it and i think the other answers are much better, but i'm wondering why this wouldn't work:
foreach (var param in pParams.Where(p => p != null)
{
types.Add(param.GetType());
}
You could approach the problem by implementing your own GetMethod that iterates through all the method in the object and determine which one is the best match, I hope this helps.
I tested the following method with the example you provided and it worked
MethodInfo SmarterGetMethod(object o, string nameMethod, params object[] args)
{
var methods = o.GetType().GetMethods();
var min = args.Length;
var values = new int[methods.Length];
values.Initialize();
//Iterates through all methods in o
for (var i = 0; i < methods.Length; i += 1)
{
if (methods[i].Name == nameMethod)
{
var parameters = methods[i].GetParameters();
if (parameters.Length == min)
{
//Iterates through parameters
for (var j = 0; j < min; j += 1)
{
if (args[j] == null)
{
if (parameters[j].ParameterType.IsValueType)
{
values[i] = 0;
break;
}
else
{
values[i] += 1;
}
}
else
{
if (parameters[j].ParameterType != args[j].GetType())
{
values[i] = 0;
break;
}
else
{
values[i] += 2;
}
}
}
if (values[i] == min * 2) //Exact match
return methods[i];
}
}
}
var best = values.Max();
if (best < min) //There is no match
return null;
//Iterates through value until it finds first best match
for (var i = 0; i < values.Length; i += 1)
{
if (values[i] == best)
return methods[i];
}
return null; //Should never happen
}
If none of parameters is NULL you perform usual method call, if one is null however
else if at least one is null you take different approach:
build parameter type list from parameters : like "int, char, null, int"
get functions overloads with same number of parameters for your function name
see whether there is just one matching function, cause if there are 2 you cannot determine which to call (hardest part but fairly straightforward I think)
call the function you figured out with your parameters and nulls

Cast an object into generic list

I have a requirement where I can get the following in an object -
a type T or List<T>
Converting object into T is easy. How can I convert it to List(by first checking that it can be converted successfully or not), reason I want to convert is to scroll through the list and call tostring on each element.
My actual code -
namespace Generic_Collection_Code
{
class Program
{
public static string DumpObj(object obj)
{
string sTemp = String.Empty;
List<int> ints = obj as List<int>;
if (ints != null)
{
foreach (int i in ints)
sTemp += i.ToString() + ",";
sTemp.Trim(',');
}
else
{
List<string> strings = obj as List<string>;
if (strings != null)
{
foreach (string s in strings)
sTemp += s + ",";
sTemp.Trim(',');
}
else
{
sTemp += obj.ToString();
}
}
return sTemp;
}
static void Main(string[] args)
{
List<int> listInts = new List<int>();
listInts.Add(1);
listInts.Add(2);
listInts.Add(3);
Console.WriteLine("Object1: {0}", DumpObj(listInts));
int i = 90;
Console.WriteLine("Object2 {0}", DumpObj(i));
List<string> listStrings = new List<string>();
listStrings.Add("1");
listStrings.Add("2");
listStrings.Add("3");
Console.WriteLine("Object3: {0}", DumpObj(listStrings));
Console.ReadKey();
}
}
}
The above code works but I know its an ugly way to achieve this. I wanted to ask from community how can I have this function like -
public static string DumpObj<T>(object obj)
{
string sTemp = String.Empty;
List<T> list = obj as List<T>;
if (list != null)
{
foreach (T i in list)
sTemp += i.ToString() + ",";
sTemp.Trim(',');
}
return sTemp;
}
This gives me compilation errors as I have to specify T while calling DumpObj with error as -
Error 1 The type arguments for method 'Generic_Collection_Code.Program.DumpObj(object)' cannot be inferred from the usage. Try specifying the type arguments explicitly. D:\DotNet\Generic_Collection_Code\Generic_Collection_Code\Program.cs 57 47 Generic_Collection_Code
as you can see, obj is an object, i dont know its type while calling dumobj.
I hope I have made myself clear on this one.
I appreciate your time!
Regards
Amit
Say
List<T> genericList = object as List<T>;
if(genericList != null)
{
// Do the loop
}
The "as" keyword verifies that "object" actually "is-a" List< T >. If so, you get a List< T > back from it. If not, you get null.
What is the compilation error you're getting? If T is declared as a generic type parameter in your context then then the only compile-time issue I can see with that statement is the use of the keyword object as a variable name. At any rate, I'd suggest something like this as best expressing your intention:
IEnumerable enumerable = obj as IEnumerable;
if (enumerable != null)
{
foreach (object item in enumerable)
{
sTemp += item.ToString();
}
}
You may also want to consider using a StringBuilder if your list is likely to have a lot of items.
you cant do this
List<T> genericList = (List<T>)object
might be you want
List<T> genericList = (List<T>)obj
where obj is object
How about combining "as" with "is"?
if (object is List<T>)
{
List<T> genericlist = object as List<T>;
// loop list
}
else if (object is T)
{
// do something else
}
How about casting the Object into System.Collections.IList (instead of the Generic version) because Generic list also implement this interface. Then cast each of them into the desired type.
Here is what I am working on..
private static void DataSourcePropertyChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs args) {
BarChart _ = sender as BarChart;
if (args.Property.Equals(BarChart.DataSourceProperty)) {
System.Collections.IList data = (System.Collections.IList)args.NewValue;
if (data == null) return;
foreach (object __ in data) {
IChartDataItem item = __ as IChartDataItem;
BarChartItem bar = new BarChartItem() {
Label = item.Label,
Value = item.Value
};
_._visualCollection.Add(bar);
if (_.MaxData < item.Value)
_.MaxData = item.Value;
}
if (_.Orientation == Orientation.Horizontal)
_.Ratio = _.Width / _.MaxData;
}
}

Categories