Saving a class to a delim file using reflection - c#

I want to write the property names and matching data to a delimited file, I've copied some code from the c# objectdumper help file and it all seems to work OK but I dont understand reflection enough to be confident to use it. What I'm worried about is an incorrect value being placed in the incorrect column, is it possible for this to happen e.g.
Field1,Field2
Val1,Val2
Val1,Val2
Val2,Val1 << Could this ever happen ?
Also what does this piece of code mean?
f != null ? f.GetValue(this) : p.GetValue(this, null)
Code below:
public string returnRec(bool header, string delim)
{
string returnString = "";
bool propWritten = false;
MemberInfo[] members = this.GetType().GetMembers(BindingFlags.Public | BindingFlags.Instance);
foreach (MemberInfo m in members)
{
FieldInfo f = m as FieldInfo;
PropertyInfo p = m as PropertyInfo;
if (f != null || p != null)
{
if (propWritten)
{
returnString += delim;
}
else
{
propWritten = true;
}
if (header)
returnString += m.Name;
else
{
Type t = f != null ? f.FieldType : p.PropertyType;
if (t.IsValueType || t == typeof(string))
{
returnString += f != null ? f.GetValue(this) : p.GetValue(this, null);
}
}
}
}
return returnString;
}

Type t = f != null ? f.FieldType : p.PropertyType;
this is an inline if, asking is f != null then f.FieldType else p.PropertyType
can be written as
Type t;
if (f != null)
t = f.FieldType;
else
t = p.PropertyType;

#astander and #Frederik have essentially answered the questions and concerns that you specifically voiced, but I'd like to suggest doing things in a slightly more efficient manner. Depending on the number of object instances that you wish to write to your file, the method that you've presented may end up being quite inefficient. That's because you're gleaning type and value information via reflection on every iteration, which is unnecessary.
What you're looking for is something that looks up type information once, and then only uses reflection to get the value of properties and fields, e.g. (.NET 3.5),
public static IEnumerable<string> ReturnRecs(IEnumerable items, bool returnHeader, string delimiter)
{
bool haveFoundMembers = false;
bool haveOutputHeader = false;
PropertyInfo[] properties = null;
FieldInfo[] fields = null;
foreach (var item in items)
{
if (!haveFoundMembers)
{
Type type = item.GetType();
properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(pi => pi.PropertyType.IsValueType || pi.PropertyType == typeof (string)).ToArray();
fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance)
.Where(fi => fi.FieldType.IsValueType || fi.FieldType == typeof(string)).ToArray();
haveFoundMembers = true;
}
if (!haveOutputHeader)
{
yield return String.Join(delimiter, properties.Select(pi => pi.Name)
.Concat(fields.Select(pi => pi.Name)).ToArray());
haveOutputHeader = true;
}
yield return String.Join(delimiter,
properties.Select(pi => pi.GetValue(item, null).ToString())
.Concat(fields.Select(fi => fi.GetValue(item).ToString())).ToArray());
}
The above code only ever performs a GetProperties and GetFields once per group of records--also, because of this, there's no need to explicitly sort the properties and fields as was #Frederik's suggestion.

#astander has already given you an answer on the Type t = f != null ? f.FieldType : p.PropertyType; question, so I will leave that one out. Regarding getting the values into the correct columns, I don't know wheter reflection guarantees to list the members of a type in a specific order, but you can guarantee it by sorting the list before using it (using Linq):
MemberInfo[] members = typeof(Item).GetMembers(BindingFlags.Public | BindingFlags.Instance);
IEnumerable<MemberInfo> sortedMembers = members.OrderBy(m => m.Name);
foreach (MemberInfo member in sortedMembers)
{
// write info to file
}
Or if you prefer a non-Linq approach (works with .NET Framework 2.0):
MemberInfo[] members = typeof(Item).GetMembers(BindingFlags.Public | BindingFlags.Instance);
Array.Sort(members, delegate(MemberInfo x, MemberInfo y){
return x.Name.CompareTo(y.Name);
});
foreach (MemberInfo member in members)
{
// write info to file
}

Just to add some thoughts re the accepted answer, in particular for large data volumes:
PropertyInfo etc can be unnecessarily slow; there are ways to avoid this, for example using HyperDescriptor or other dynamic code
rather than building lots of intermediate strings, it may be more efficient to write output directly to a TextWriter
As a tweaked version that adopts these approaches, see below; note that I haven't enabled HyperDescriptor in this example, but that is just:
HyperTypeDescriptionProvider.Add(typeof(YourType));
Anyway...
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
static class Program {
static void Main() { // just some test data...
var data = new[] { new { Foo = "abc", Bar = 123 }, new { Foo = "def", Bar = 456 } };
Write(data, Console.Out, true, "|");
}
public static void Write<T>(IEnumerable<T> items, TextWriter output, bool writeHeaders, string delimiter) {
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
foreach (T item in items) {
bool firstCol = true;
if (writeHeaders) {
foreach (PropertyDescriptor prop in properties) {
if (firstCol) {
firstCol = false;
} else {
output.Write(delimiter);
}
output.Write(prop.Name);
}
output.WriteLine();
writeHeaders = false;
firstCol = true;
}
foreach (PropertyDescriptor prop in properties) {
if (firstCol) {
firstCol = false;
} else {
output.Write(delimiter);
}
output.Write(prop.Converter.ConvertToString(prop.GetValue(item)));
}
output.WriteLine();
}
}
}

Related

Clean way to check if all properties, except for two, matches between two objects? [duplicate]

This question already has answers here:
Comparing object properties in c# [closed]
(20 answers)
Closed 4 years ago.
I have a database containing components with about 20 properties. To find out if an update is needed I want to check if all properties for the two objects, except DateCreated and Id, matches.
If all matches no update, if not, update db.
Component comp_InApp = new Component()
{
Id = null,
Description = "Commponent",
Price = 100,
DateCreated = "2019-01-30",
// Twenty more prop
};
Component comp_InDb = new Component()
{
Id = 1,
Description = "Component",
Price = 100,
DateCreated = "2019-01-01",
// Twenty more prop
};
// Check if all properties match, except DateCreated and Id.
if (comp_InApp.Description == comp_InDb.Description &&
comp_InApp.Price == comp_InDb.Price
// Twenty more prop
)
{
// Everything up to date.
}
else
{
// Update db.
}
This works, but it's not a very clean way with 20 properties. Is there a better way of achieiving the same result in a cleaner way?
I am using DeepEqual when I don't want/don't have the time to write myself Equals and GetHashCode methods.
You can install it simply from NuGet with:
Install-Package DeepEqual
and use it like:
if (comp_InApp.IsDeepEqual(comp_InDb))
{
// Everything up to date.
}
else
{
// Update db.
}
But keep in mind that this will only work for your case when you want to explicitly compare objects, but not for the case when you want to remove an object form a List or cases like this, when Equals and GetHashCode are invoked.
One way, create a class that implements IEqualityComparer<Component> to encapsulate this logic and to avoid that you have modify the class Comparer itself(if you don't want this Equals logic all time). Then you can use it for a simple Equals of two instances of Component and even for all LINQ methods that accepts it as additional argument.
class ComponentComparer : IEqualityComparer<Component>
{
public bool Equals(Component x, Component y)
{
if (object.ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;
return x.Price == y.Price && x.Description == y.Description;
}
public int GetHashCode(Component obj)
{
unchecked
{
int hash = 17;
hash = hash * 23 + obj.Price.GetHashCode();
hash = hash * 23 + obj.Description?.GetHashCode() ?? 0;
// ...
return hash;
}
}
}
Your simple use-case:
var comparer = new ComponentComparer();
bool equal = comparer.Equals(comp_InApp, comp_InDb);
It works also if you have two collections and want to know the difference, for example:
IEnumerable<Component> missingInDb = inAppList.Except( inDbList, comparer );
Here is a solution with Reflection:
static bool AreTwoEqual(Component inApp, Component inDb)
{
string[] propertiesToExclude = new string[] { "DateCreated", "Id" };
PropertyInfo[] propertyInfos = typeof(Component).GetProperties()
.Where(x => !propertiesToExclude.Contains(x.Name))
.ToArray();
foreach (PropertyInfo propertyInfo in propertyInfos)
{
bool areSame = inApp.GetType().GetProperty(propertyInfo.Name).GetValue(inApp, null).Equals(inDb.GetType().GetProperty(propertyInfo.Name).GetValue(inDb, null));
if (!areSame)
{
return false;
}
}
return true;
}
You can use a Reflection but it may slow your application. An alternative way of creating that comparator is to generate it with Linq Expressions. Try this code:
public static Expression<Func<T, T, bool>> CreateAreEqualExpression<T>(params string[] toExclude)
{
var type = typeof(T);
var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => !toExclude.Contains(p.Name))
.ToArray();
var p1 = Expression.Parameter(type, "p1");
var p2 = Expression.Parameter(type, "p2");
Expression body = null;
foreach (var property in props)
{
var pare = Expression.Equal(
Expression.PropertyOrField(p1, property.Name),
Expression.PropertyOrField(p2, property.Name)
);
body = body == null ? pare : Expression.AndAlso(body, pare);
}
if (body == null) // all properties are excluded
body = Expression.Constant(true);
var lambda = Expression.Lambda<Func<T, T, bool>>(body, p1, p2);
return lambda;
}
it will generate an expression that looks like
(Component p1, Component p2) => ((p1.Description == p2.Description) && (p1.Price == p2.Price))
Usage is simple
var comporator = CreateAreEqualExpression<Component>("Id", "DateCreated")
.Compile(); // save compiled comparator somewhere to use it again later
var areEqual = comporator(comp_InApp, comp_InDb);
EDIT: to make it more type safe you can exclude properties using lambdas
public static Expression<Func<T, T, bool>> CreateAreEqualExpression<T>(
params Expression<Func<T, object>>[] toExclude)
{
var exclude = toExclude
.Select(e =>
{
// for properties that is value types (int, DateTime and so on)
var name = ((e.Body as UnaryExpression)?.Operand as MemberExpression)?.Member.Name;
if (name != null)
return name;
// for properties that is reference type
return (e.Body as MemberExpression)?.Member.Name;
})
.Where(n => n != null)
.Distinct()
.ToArray();
var type = typeof(T);
var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => !exclude.Contains(p.Name))
.ToArray();
/* rest of code is unchanged */
}
Now when using it we have an IntelliSense support:
var comparator = CreateAreEqualExpression<Component>(
c => c.Id,
c => c.DateCreated)
.Compile();

Dynamic casting to a generic type

I'm trying to get to the Cases.Values property of System.Activities.Statements.Switch object. The problem is that I cannot cast Activity object at runtime to Switch<> type (which derives from it).
var switchType = activity.GetType();
bool isSwitch = (switchType.IsGenericType && switchType.GetGenericTypeDefinition() == typeof(Switch<>));
if (isSwitch)
{
Type[] genericArgumentTypes = switchType.GetGenericArguments();
if (genericArgumentTypes.Length > 0)
{
var switchStatement = (Switch<genericArgumentTypes[0]>) activity; //that's incorrect
foreach (var aCase in switchStatement.Cases.Values)
{
ProcessActivity(aCase, dataSets, context);
}
}
}
Also,
dynamic switchStatement = activity;
foreach (var aCase in switchStatement.Cases.Values)
{
ProcessActivity(aCase, dataSets, context);
}
throws an error, that the property is not there, while debugger is showing it's not true. The T is irrelevant for me - I need only the Cases collection.
EDIT
Actually, I've found even cleaner solution, than the one I set as an answer.
dynamic switchStatement = activity;
var cases = switchStatement.Cases as IEnumerable;
if (cases != null)
{
foreach (dynamic aCase in cases)
{
ProcessActivity(aCase.Value);
}
}
You can't.
But instead of your loop, put:
var process = typeof(CurrentHelperClass).GetMethod("ProcessSwitch`1").MakeGenericMethod(typeof(genericArgumentTypes[0]));
process.Invoke(null,new object[]{activity});
and define a new method in the same class:
static void ProcessSwitch<T>(Switch<T> switchStatement)
{
foreach (var aCase in switchStatement.Cases.Values)
{
ProcessActivity(aCase, dataSets, context);
}
}
var switchType = activity.GetType();
var prop = switchType.GetProperty("Cases", System.Reflection.BindingFlags.Public
| System.Reflection.BindingFlags.Instance); //move it outside of the method and use the same property for every time you call the method since it's performance expansive.
bool isSwitch = (switchType.IsGenericType && switchType.GetGenericTypeDefinition() == typeof(Switch<>));
if (isSwitch)
{
IEnumerable dictionary = prop.GetValue(activity,null) as IDictionary;
foreach (var item in dictionary.Values)
{
ProcessActivity(item, dataSets, context);
}
}

Clear all string members from object in C#

I have a class that contains about 500 String members and I'd like to "reset" them all by setting them to String.Empty. Can anybody tell me how to do this using reflection so I can iterate over every String member ?
Thanks
typeof(MyClass).GetProperties()
.Where(p => p.PropertyType == typeof(string))
.ToList()
.ForEach(p => p.SetValue(myObj,string.Empty, null));
EDIT:
If you are dealing with fields and not properties, it's very similar
typeof(MyClass).GetFields()
.Where(f => f.FieldType == typeof(string))
.ToList()
.ForEach(f => f.SetValue(myObj,string.Empty));
foreach (PropertyInfo pi in MyObj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic ).ToArray() )
{
if (pi.PropertyType == typeof(string))
{
pi.SetValue(MyObj, string.Empty, null);
}
}
For fields use
foreach (FieldInfo fi in MyObj.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic ).ToArray() )
{
if (fi.FieldType == typeof(string))
{
fi.SetValue(MyObj, string.Empty);
}
}
Same code implemented using compiled Expression-Tree:
private static Action<TObject> CreateClearStringProperties<TObject>()
{
var memberAccessExpressions = new List<System.Linq.Expressions.Expression>();
System.Linq.Expressions.ParameterExpression arg = System.Linq.Expressions.Expression.Parameter(typeof(TObject), "x");
foreach (var propertyInfo in typeof(TObject).GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.FlattenHierarchy))
{
if (propertyInfo.PropertyType == typeof(string) && propertyInfo.SetMethod != null)
{
var memberAccess = System.Linq.Expressions.Expression.MakeMemberAccess(arg, propertyInfo);
var assignment = System.Linq.Expressions.Expression.Assign(memberAccess, System.Linq.Expressions.Expression.Constant(string.Empty));
memberAccessExpressions.Add(assignment);
}
}
foreach (var fieldInfo in typeof(TObject).GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.FlattenHierarchy))
{
if (fieldInfo.FieldType == typeof(string))
{
var memberAccess = System.Linq.Expressions.Expression.MakeMemberAccess(arg, fieldInfo);
var assignment = System.Linq.Expressions.Expression.Assign(memberAccess, System.Linq.Expressions.Expression.Constant(string.Empty));
memberAccessExpressions.Add(assignment);
}
}
if (memberAccessExpressions.Count == 0)
return new Action<TObject>((e) => { });
var allProperties = System.Linq.Enumerable.Aggregate(memberAccessExpressions, System.Linq.Expressions.Expression.Block);
return System.Linq.Expressions.Expression.Lambda<Action<TObject>>(allProperties, arg).Compile();
}
Example of how to use it:
Action<MyClass> clearObject = CreateClearStringProperties<MyClass>();
MyClass obj = new MyClass();
clearObject(obj);
Needed it for some generic pooling logic, where objects put back into the pool should not keep obsolete string-objects alive.

Same Variable Names - 2 Different Classes - How To Copy Values From One To Another - Reflection - C#

Without using AutoMapper... (because someone in charge of this project will shit bricks when they see dependencies)
I have a class (class A) with however many properties. I have another class (class B) with those same properties (same names and type). Class B could also have other un related variables.
Is there some simple reflection code that can copy values from class A to class B?
The simpler the better.
Type typeB = b.GetType();
foreach (PropertyInfo property in a.GetType().GetProperties())
{
if (!property.CanRead || (property.GetIndexParameters().Length > 0))
continue;
PropertyInfo other = typeB.GetProperty(property.Name);
if ((other != null) && (other.CanWrite))
other.SetValue(b, property.GetValue(a, null), null);
}
This?
static void Copy(object a, object b)
{
foreach (PropertyInfo propA in a.GetType().GetProperties())
{
PropertyInfo propB = b.GetType().GetProperty(propA.Name);
propB.SetValue(b, propA.GetValue(a, null), null);
}
}
If you will use it for more than one object then it may be useful to get mapper:
public static Action<TIn, TOut> GetMapper<TIn, TOut>()
{
var aProperties = typeof(TIn).GetProperties();
var bType = typeof (TOut);
var result = from aProperty in aProperties
let bProperty = bType.GetProperty(aProperty.Name)
where bProperty != null &&
aProperty.CanRead &&
bProperty.CanWrite
select new {
aGetter = aProperty.GetGetMethod(),
bSetter = bProperty.GetSetMethod()
};
return (a, b) =>
{
foreach (var properties in result)
{
var propertyValue = properties.aGetter.Invoke(a, null);
properties.bSetter.Invoke(b, new[] { propertyValue });
}
};
}
I know you asked for reflection code and It's an old post but I have a different suggestion and wanted to share it. It could be more faster than reflection.
You can serialize the input object to json string, then deserialize to output object. All the properties with same name will assign to new object's properties automatically.
var json = JsonConvert.SerializeObject(a);
var b = JsonConvert.DeserializeObject<T>(json);
Try this:-
PropertyInfo[] aProps = typeof(A).GetProperties(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Default | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static);
PropertyInfo[] bProps = typeof(B).GetProperties(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Default | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static);
foreach (PropertyInfo pi in aProps)
{
PropertyInfo infoObj = bProps.Where(info => info.Name == pi.Name).First();
if (infoObj != null)
{
infoObj.SetValue(second, pi.GetValue(first, null), null);
}
}

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

Categories