I currently have two objects (of the same type) that may represent any primitive value such as string, int, datetime etc.
var valueX = ...;
var valueY = ...;
Atm I compare them on string level like this
var result = string.Compare(fieldValueX.ToString(), fieldValueY.ToString(), StringComparison.Ordinal);
But I need to compare them on type level (as ints if those happen to be ints
int i = 0;
int j = 2;
i.CompareTo(j);
, as dates if they happen to be date etc), something like
object.Compare(x,y);
That returns -1,0,1 in the same way. What are the ways to achieve that ?
Thanks for your answers, the correct way was to check if the object implements IComparable and if it does - make a typecast and call CompareTo
if (valueX is IComparable)
{
var compareResult = ((IComparable)valueX).CompareTo((IComparable)valueY);
}
Object1.Equals(obj1, obj2) wont work unless #object is referencing the same object.
EG:
var obj1 = new MyObject();
var obj2 = new MyObject();
This will return "False" for Object1.Equals(obj1, obj2) as they are different ref's
var obj1 = new MyObject();
var obj2 = obj1;
This will return "True" for Object1.Equals(obj1, obj2) as they are the same ref.
Solution:
You will most likely need to write an extension method that overrides Object.Equals. either create a custom object comparer for a specific type (See here for custom object comparer:) or you can dynamically go through each property and compare.
There's several options to do this.
Override Object.Equal
You can override the Object.Equal() method in the class, and then determine what makes the objects equal there. This can also let you cleverly decide what to compare, since it appears those objects can be multiple data types. Inside this override, you'll need to handle each possible case. You can read more about this option here:
https://msdn.microsoft.com/en-us/library/bsc2ak47(v=vs.110).aspx
It should be noted by default, Object.Equal() will compare your objects references.
Implement IComparable
IComparable is a neat interface that gives an object Compare. As the comments mention, this will let you define how to compare the objects based on whatever criteria you want.
This option gets covered here: https://msdn.microsoft.com/en-us/library/system.icomparable(v=vs.110).aspx
Implement CompareBy() Methods
Alternatively, you can implement methods for each possible type, ie CompareByInt() or CompareByString(), but this method depends on you knowing what you're going to have when you go to do it. This will also have the negative effect of making code more difficult to maintain, as there's many more methods involved.
You can write a GeneralComparer with a Compare method, overloaded as necessary.
For types that must perform a standard comparison you can use EqualityComparer<T>.Default; for other types you write your own comparison function. Here's a sample:
static class GeneralComparer
{
public static int Compare(int x, int y)
{
//for int, use the standard comparison:
return EqualityComparer<int>.Default.Equals(x, y);
}
public static int Compare(string x, string y)
{
//for string, use custom comparison:
return string.Compare(x, y, StringComparison.Ordinal);
}
//overload for DateTime
//overload for MyType
//overload for object
//...
}
The correct overload is chosen at runtime.
There's a drawback: if you declare two int (or other specific types) as object, the object overload is called:
object a = 2;
object b = 3;
//this will call the "Compare(object x, object y)" overload!
int comparison = GeneralComparer.Compare(a, b);
converting the objects to dictionary, then following math set(s) concept subtract them, result items should be empty in case they are identically.
public static IDictionary<string, object> ToDictionary(this object source)
{
var fields = source.GetType().GetFields(
BindingFlags.GetField |
BindingFlags.Public |
BindingFlags.Instance).ToDictionary
(
propInfo => propInfo.Name,
propInfo => propInfo.GetValue(source) ?? string.Empty
);
var properties = source.GetType().GetProperties(
BindingFlags.GetField |
BindingFlags.GetProperty |
BindingFlags.Public |
BindingFlags.Instance).ToDictionary
(
propInfo => propInfo.Name,
propInfo => propInfo.GetValue(source, null) ?? string.Empty
);
return fields.Concat(properties).ToDictionary(key => key.Key, value => value.Value); ;
}
public static bool EqualsByValue(this object source, object destination)
{
var firstDic = source.ToFlattenDictionary();
var secondDic = destination.ToFlattenDictionary();
if (firstDic.Count != secondDic.Count)
return false;
if (firstDic.Keys.Except(secondDic.Keys).Any())
return false;
if (secondDic.Keys.Except(firstDic.Keys).Any())
return false;
return firstDic.All(pair =>
pair.Value.ToString().Equals(secondDic[pair.Key].ToString())
);
}
public static bool IsAnonymousType(this object instance)
{
if (instance == null)
return false;
return instance.GetType().Namespace == null;
}
public static IDictionary<string, object> ToFlattenDictionary(this object source, string parentPropertyKey = null, IDictionary<string, object> parentPropertyValue = null)
{
var propsDic = parentPropertyValue ?? new Dictionary<string, object>();
foreach (var item in source.ToDictionary())
{
var key = string.IsNullOrEmpty(parentPropertyKey) ? item.Key : $"{parentPropertyKey}.{item.Key}";
if (item.Value.IsAnonymousType())
return item.Value.ToFlattenDictionary(key, propsDic);
else
propsDic.Add(key, item.Value);
}
return propsDic;
}
originalObj.EqualsByValue(messageBody); // will compare values.
source of the code
Related
I have a use case where I need to check if a value is a C# 7 ValueTuple, and if so, loop through each of the items. I tried checking with obj is ValueTuple and obj is (object, object) but both of those return false. I found that I could use obj.GetType().Name and check if it starts with "ValueTuple" but that seems lame to me. Any alternatives would be welcomed.
I also have the issue of getting each item. I attempted to get Item1 with the solution found here: How do I check if a property exists on a dynamic anonymous type in c#? but ((dynamic)obj).GetType().GetProperty("Item1") returns null. My hope was that I could then do a while to get each item. But this does not work. How can I get each item?
Update - more code
if (item is ValueTuple) //this does not work, but I can do a GetType and check the name
{
object tupleValue;
int nth = 1;
while ((tupleValue = ((dynamic)item).GetType().GetProperty($"Item{nth}")) != null && //this does not work
nth <= 8)
{
nth++;
//Do stuff
}
}
Structures do not inherit in C#, so ValueTuple<T1>, ValueTuple<T1,T2>, ValueTuple<T1,T2,T3> and so on are distinct types that do not inherit from ValueTuple as their base. Hence, obj is ValueTuple check fails.
If you are looking for ValueTuple with arbitrary type arguments, you can check if the class is ValueTuple<,...,> as follows:
private static readonly Set<Type> ValTupleTypes = new HashSet<Type>(
new Type[] { typeof(ValueTuple<>), typeof(ValueTuple<,>),
typeof(ValueTuple<,,>), typeof(ValueTuple<,,,>),
typeof(ValueTuple<,,,,>), typeof(ValueTuple<,,,,,>),
typeof(ValueTuple<,,,,,,>), typeof(ValueTuple<,,,,,,,>)
}
);
static bool IsValueTuple2(object obj) {
var type = obj.GetType();
return type.IsGenericType
&& ValTupleTypes.Contains(type.GetGenericTypeDefinition());
}
To get sub-items based on the type you could use an approach that is not particularly fast, but should do the trick:
static readonly IDictionary<Type,Func<object,object[]>> GetItems = new Dictionary<Type,Func<object,object[]>> {
[typeof(ValueTuple<>)] = o => new object[] {((dynamic)o).Item1}
, [typeof(ValueTuple<,>)] = o => new object[] {((dynamic)o).Item1, ((dynamic)o).Item2}
, [typeof(ValueTuple<,,>)] = o => new object[] {((dynamic)o).Item1, ((dynamic)o).Item2, ((dynamic)o).Item3}
, ...
};
This would let you do this:
object[] items = null;
var type = obj.GetType();
if (type.IsGeneric && GetItems.TryGetValue(type.GetGenericTypeDefinition(), out var itemGetter)) {
items = itemGetter(obj);
}
Regarding the part of the question "How can I get each item?"...
Both ValueTuple and Tuple both implement ITuple, which has a length property and an indexer property. So a the following console app code lists the values to the console:
// SUT (as a local function)
IEnumerable<object> GetValuesFromTuple(System.Runtime.CompilerServices.ITuple tuple)
{
for (var i = 0; i < tuple.Length; i++)
yield return tuple[i];
}
// arrange
var valueTuple = (StringProp: "abc", IntProp: 123, BoolProp: false, GuidProp: Guid.Empty);
// act
var values = GetValuesFromTuple(valueTuple);
// assert (to console)
Console.WriteLine($"Values = '{values.Count()}'");
foreach (var value in values)
{
Console.WriteLine($"Value = '{value}'");
}
Console output:
Values = '4'
Value = 'abc'
Value = '123'
Value = 'False'
Value = '00000000-0000-0000-0000-000000000000'
This is my solution to the problem. A PCL compatible extension class. Special thanks to #dasblinkenlight and #Evk for helping me out!
public static class TupleExtensions
{
private static readonly HashSet<Type> ValueTupleTypes = new HashSet<Type>(new Type[]
{
typeof(ValueTuple<>),
typeof(ValueTuple<,>),
typeof(ValueTuple<,,>),
typeof(ValueTuple<,,,>),
typeof(ValueTuple<,,,,>),
typeof(ValueTuple<,,,,,>),
typeof(ValueTuple<,,,,,,>),
typeof(ValueTuple<,,,,,,,>)
});
public static bool IsValueTuple(this object obj) => IsValueTupleType(obj.GetType());
public static bool IsValueTupleType(this Type type)
{
return type.GetTypeInfo().IsGenericType && ValueTupleTypes.Contains(type.GetGenericTypeDefinition());
}
public static List<object> GetValueTupleItemObjects(this object tuple) => GetValueTupleItemFields(tuple.GetType()).Select(f => f.GetValue(tuple)).ToList();
public static List<Type> GetValueTupleItemTypes(this Type tupleType) => GetValueTupleItemFields(tupleType).Select(f => f.FieldType).ToList();
public static List<FieldInfo> GetValueTupleItemFields(this Type tupleType)
{
var items = new List<FieldInfo>();
FieldInfo field;
int nth = 1;
while ((field = tupleType.GetRuntimeField($"Item{nth}")) != null)
{
nth++;
items.Add(field);
}
return items;
}
}
hackish one liner
type.Name.StartsWith("ValueTuple`")
(can be extended to check the digit at the end)
I have a function that generates objects with different data in it (the function fills the object with random data, according to the type). The function returns an object[] as the type is only know at runtime (and it's passed to the function as a parameter).
double[] values;
values = factory.GetData(typeof(double), 10);
Unfortunately I get a compiler error:
Cannot convert from object[] to double[].
How can I cast the object[] programmatically?
EDIT:
this is the original function:
public object[] GetData(Type type, int howMany)
{
var data = new List<object>();
for (var i = 0; i < howMany; i++)
{
data.Add(Convert.ChangeType(GetRandom(type), type));
}
return data.ToArray();
}
where GetRandom() create an object of type type and assign it a random value (random int, random string, random double, only basic types)
and this is the GetRandom() function:
public T GetRandom<T>()
{
var type = typeof(T);
if (type == typeof(int))
{
return prng.Next(0, int.MaxValue);
}
if (type == typeof(double))
{
return prng.NextDouble();
}
if (type == typeof(string))
{
return GetString(MinStringLength, MaxStringLength);
}
if (type == typeof(DateTime))
{
var tmp = StartTime;
StartTime += new TimeSpan(Interval * TimeSpan.TicksPerMillisecond);
return tmp;
}
}
Use Array.ConvertAll:
values = Array.ConvertAll(factory.GetData(typeof(double), 10), item => (double)item);
Example:
object[] input = new object[]{1.0, 2.0, 3.0};
double[] output = Array.ConvertAll(input, element => (double)element); // [1.0, 2.0, 3.0]
Note you might get InvalidCastException if one of the items can't be casted to double.
You could:
values = factory.GetData(typeof(double), 10).Cast<double>().ToArray();
If factory.GetData return an array of object of double, you can use:
values = factory.GetData(typeof(double), 10).Cast<double>().ToArray();
Now all the answers (mostly) actually answer the question there are none that actually talk about using Generics instead. Now this may not fit your direct bill but can be added quite easily to resolve the issue and require no knowledge from a calling application how to understand the return values.
This is simple. Just define an overload that accepts a Generic Type (note T)
public T[] GetData<T>(int count)
{
Type tType = typeof(T);
//.. TODO: Generate our array.. anyway you wish..
List<T> list = new List<T>();
for (int i = 0; i < count; i++)
list.Add(Activator.CreateInstance<T>());
return list.ToArray();
}
So this is a basic example and callable by:
Factory factory = new Factory();
var arr = factory.GetData<double>(10); //returns a typed array of double
Now from a caller perspective we know that the data we are receivining is typed to double or the type they provide.
This is an alternative to your initial question. However if your array of objects will not always be the type originally requested then this will not work.
EDIT
To define the array is really up to how you define your objects but lets just take your initial concept and adapt it to the same above:
public T[] GetData<T>(int count)
{
Type tType = typeof(T);
//.. TODO: Generate our array.. anyway you wish..
List<T> list = new List<T>();
for (int i = 0; i < count; i++)
list.Add((T)GetRandom(tType));
return list.ToArray();
}
In the new sample we are assuming that the Method GetRandom() will return the Type requested. The type requested is generic based on the typereference (typeparam) T. We can get the actual type by calling typeof(T). Now in this example we simply directly cast the GetRandom() object response (I am assuming GetRandom() returns a type of object.
Final Edit
As stated in the comments your can change your object GetRandom(Type type) to T GetRandom<T>(). This will allow you to generate specific types for your random. I would suggest reading up on Generics https://msdn.microsoft.com/en-us/library/512aeb7t.aspx
Now one thing that is not quickly apparent is that what you name your generic is up to you. You dont have to use T and you can use multiple generics in one method call, as with many methods you probably already use.
** Final Final Edit **
Just to elaborate how you could change your GetRandom method to a generic we still have to work with the type object its really the only one that allows for direct boxing conversion for any type. You could use the as keyword but that will could leave to other problems. Now the GetRandom(Type type) method is returning a random object of the type. As stated this is limited to a few types so lets just put together an example.
The first thing to understand is how to handle our various types. Now personally I like to define an interface. So lets define an interface for all our Random Types to inherit. As below:
interface IRandomTypeBuilder
{
object GetNext();
}
As simple interface to return a random typed entity of with the method of GetNext(). This will return a typed response based on the generic parameter T.
Now some simple implementations of this interface.
class DoubleRandomBuilder : IRandomTypeBuilder
{
static Random rng = new Random();
public object GetNext()
{
return rng.NextDouble() * rng.Next(0, 1000);
}
}
class IntRandomBuilder : IRandomTypeBuilder
{
static Random rng = new Random();
public object GetNext()
{
return rng.Next(int.MinValue, int.MaxValue);
}
}
class StringRandomBuilder : IRandomTypeBuilder
{
static Random rng = new Random();
static string aplha = "abcdefghijklmnopqrstuvwxyz";
public object GetNext()
{
string next = "";
for (int i = rng.Next(4, 10), j = i + rng.Next(1, 10); i < j; i++)
next += aplha[rng.Next(0, aplha.Length)];
return next;
}
}
class BoolRandomBuilder : IRandomTypeBuilder
{
static Random rng = new Random();
public object GetNext()
{
return rng.Next(0, 2) % 2 == 0;
}
}
Yes these are very simple but we have 4 different types that all define the GetNext() method and return a random value for the type. Now we can define the GetRandom<T>() method.
public T GetRandom<T>()
{
Type tType = typeof(T);
IRandomTypeBuilder typeGenerator = null;
if (tType == typeof(double))
typeGenerator = new DoubleRandomBuilder();
else if (tType == typeof(int))
typeGenerator = new IntRandomBuilder();
else if (tType == typeof(string))
typeGenerator = new StringRandomBuilder();
else if (tType == typeof(bool))
typeGenerator = new BoolRandomBuilder();
return (T)(typeGenerator == null ? default(T) : typeGenerator.GetNext());
}
Assuming GetData does return doubles boxed as arrays, you can use Cast() to cast all elements to double:
values=factory.GetData(typeof(double), 10).Cast<double>().ToArray();
or you can use OfType() to filter out values that are not double
values=factory.GetData(typeof(double), 10).OfType<double>().ToArray();
A better option though would be to rewrite GetData as a generic method and return a T[]
Consider this chunk of code where I'm testing an unknown variable that could be an int, MyObj, or Tuple, and I'm doing some type checking to see what it is so that I can go on and handle the data differently depending on what it is:
class MyObj { }
// ...
void MyMethod(object data) {
if (data is int) Console.Write("Datatype = int");
else if (data is MyObj) Console.Write("Datatype = MyObj");
else if (data is Tuple<object,object>) {
var myTuple = data as Tuple<object,object>;
if (myTuple.Item1 is int && myTuple.Item2 is int) Console.WriteLine("Datatype = Tuple<int,int>");
else if (myTuple.Item1 is int && myTuple.Item2 is MyObj) Console.WriteLine("Datatype = Tuple<int,MyObj>");
// other type checks
}
}
// ...
MyMethod(1); // Works : Datatype = int
MyMethod(new MyObj()); // Works : Datatype = MyObj
MyMethod(Tuple.Create(1, 2)); // Fails
MyMethod(Tuple.Create(1, new MyObj()); // Fails
// and also...
var items = new List<object>() {
1,
new MyObj(),
Tuple.Create(1, 2),
Tuple.Create(1, new MyObj())
};
foreach (var o in items) MyMethod(o);
My problem is that a Tuple<int,MyObj> doesn't cast to Tuple<object,object>, yet individually, I can cast int to object and MyObj to object.
How do I do the cast?
If you want to check for any pair type, you'll have to use reflection:
Type t = data.GetType();
if(t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Tuple<,>))
{
var types = t.GetGenericArguments();
Console.WriteLine("Datatype = Tuple<{0}, {1}>", types[0].Name, types[1].Name)
}
You may be able to use overloading and dynamic instead of manually inspecting the type:
MyMethod(MyObject obj) { ... }
MyMethod(int i) { ... }
MyMethod(Tuple<int, int> t) { ... }
MyMethod(Tuple<int, MyObject> t) { ... }
foreach(dynamic d in items)
{
MyMethod(d);
}
this will choose the best overload at runtime so you have access the tuple types directly.
As you can see here, Tuple<T1, T2> is not covariant.
Rather than trying to make one method that can take any type of parameter, why not overload your function to recieve valid types you actually expect.
perhaps,
void MyMethod<T1,T2>(Tuple<T1, T2> data)
{
// In case ToString() is overridden
Console.WriteLine("Datatype = Tuple<{0}, {1}>",
typeof(T1).Name,
typeof(T2).Name);
}
void MyMethod(MyObj data)
{
Console.WriteLine("Datatype = MyObj");
}
void MyMethod(int data)
{
Console.WriteLine("Datatype = System.Int32");
}
This way no type checking is required, the compiler does it for you at compile time. This is not javascript, strong typing can be a benefit.
In your code you are explicitly checking against int and MyObj, as well as against their position (Item1 or Item2) as seen here:
if (myTuple.Item1 is int && myTuple.Item2 is int)
else if (myTuple.Item1 is int && myTuple.Item2 is MyObj)
You can do the same explicit check using the same framework you already wrote:
if (data is int) Console.WriteLine("Datatype = int");
else if (data is MyObj) Console.WriteLine("Datatype = MyObj");
else if (data is Tuple<int, int>) Console.WriteLine("Datatype = Tuple<int,int>");
else if (data is Tuple<int, MyObj>) Console.WriteLine("Datatype = Tuple<int,MyObj>");
The downsides to the above code are the same as what you were attempting to do with Tuple<object, object>: You have to explicitly check all combinations of what could go into the tuple as well as their positions, i.e., my code will not work as written if you pass in Tuple<double, double> or Tuple<MyObj, int>, but neither would your code if it actually worked as you wanted it to. If you don't want explicit/hardcoded values then it appears you have to use reflection as seen in Lee's answer, otherwise the above code does what you were intending with less work.
Don’t know why I can’t figure this out…
I am using a comparison method that takes two types, loops through them, and using reflection builds a report of differences of the fields. This is more or less a detailed comparison of the two types. The types have the same properties. While I am doing the comparison, each of the fields also has a custom attribute associated with it. In the below, the Difference object stores the names, and other properties related to the comparison operation.
To do the comparison, I use the following:
public static List<Difference<T>> Detailed Difference <T>(this T val1, T val2)
{
List< Difference <T>> differences = new List< Difference <T>>();
FieldInfo[] fi = val1.GetType().GetFields();
foreach (FieldInfo f in fi)
{
Difference <T> v = new Difference <T>();
v.Prop = f.Name;
v.ValA = f.GetValue(val1);
v.ValB = f.GetValue(val2);
v.ObjectA = val1;
v.ObjectB = val2;
v.MyCustomAttribute = (MyCustomAttribute Attribute) Attribute.GetCustomAttribute(f, typeof (MyCustomAttribute Attribute));
differences.Add(v)
}
return differences;
}
MyCustomAttribute is an enum that contains FooA and FooB.
If MyCustomAttribute equals FooA, the value of ValA should be used to build a return of type T to add to the method List return. If MyCustomAttribute equals FooB, the value of ValB should be used when building the new type. The problem I am having is the generic doesn’t allow me to instantiate a new type of T, for pretty obvious reasons...so I can’t figure out how to, more or less, map the property values based on reading the custom attribute.
If you include the new constraint, then new T() will be possible. Then you can use FieldInfo.SetValue to set the values in it.
public static List<Difference<T>> Detailed Difference<T>(this T val1, T val2)
where T : new()
{
T newValue = new T();
// later...
v.MyCustomAttribute = // whatever it is
if (v.MyCustomAttribute == MyCustomAttribute.FooA)
f.SetValue(newValue, v.ValA);
else if (v.MyCustomAttribute == MyCustomAttribute.FooB)
f.SetValue(newValue, v.ValB);
differences.Add(v);
// other stuff...
}
If this won't work with your types, you'll need something passed in to your method. Since you apparently only need one new object, and always one object, you could just take another T:
public static List<Difference<T>> Detailed Difference<T>(this T val1, T val2,
T newValue)
{
// call like
var diff = myClass1.Difference(myClass2, new MyClass(someParam));
For more advanced scenarios, take a Func<T>, so that you can call it whenever needed:
public static List<Difference<T>> Detailed Difference<T>(this T val1, T val2,
Func<T> getNewValue)
{
T newValue = getNewValue();
// call like
var diff = myClass1.Difference(myClass2, () => new MyClass(someParam));
I'm writing an application that runs "things" to a schedule.
Idea being that the database contains assembly, method information and also the parameter values. The timer will come along, reflect the method to be run, add the parameters and then execute the method.
Everything is fine except for the parameters.
So, lets say the method accepts an ENUM of CustomerType where CustomerType has two values of CustomerType.Master and CustomerType.Associate.
EDIT
I don't know the type of parameter that will be getting passed in. ENUM used as an example
END OF EDIT
We want to run Method "X" and pass in parameter "CustomerType.Master". In the database, there will be a varchar entry of "CustomerType.Master".
How do I convert the string "CustomerType.Master" into a type of CustomerType with a value of "Master" generically?
Thanks in advance,
Jim
OK, the scope of the question shifted but my original observation and objection to some other solutions still stands.
I think you don't/can't want to use 'generics' here. You don't know the type ahead of time, and since you will need to create the type, there is no need to use a generic implementation because MethodBase.Invoke takes an array of Object.
This code assumes you are instantiating the target from database field. If not just adjust accordingly.
Of course this is not all encompassing and has no useful exception handling, but it will allow you to dynamically execute arbitrary methods on an arbitrary type with arbitrary parameters values all coming from string values in a row.
NOTE: there are many many many scenarios in which this simple executor will not work. You will need to ensure that you engineer your dynamic methods to cooperate with whatever strategy you do end up deciding to use.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Reflection;
using NUnit.Framework;
namespace DynamicMethodInvocation
{
[TestFixture]
public class Tests
{
[Test]
public void Test()
{
// from your database
string assemblyQualifiedTypeName = "DynamicMethodInvocation.TestType, DynamicMethodInvocation";
string methodName = "DoSomething";
// this is how you would get the strings to put in your database
string enumString = Executor.ConvertToString(typeof(AttributeTargets), AttributeTargets.Assembly);
string colorString = Executor.ConvertToString(typeof(Color), Color.Red);
string stringString = "Hmm... String?";
object result = Executor.ExecuteMethod(assemblyQualifiedTypeName, methodName,
new[] { enumString, colorString, stringString });
Assert.IsInstanceOf<bool>(result);
Assert.IsTrue((bool)result);
}
}
public class TestType
{
public bool DoSomething(AttributeTargets #enum, Color color, string #string)
{
return true;
}
}
public class Executor
{
public static object ExecuteMethod(string assemblyQualifiedTypeName, string methodName,
string[] parameterValueStrings)
{
Type targetType = Type.GetType(assemblyQualifiedTypeName);
MethodBase method = targetType.GetMethod(methodName);
ParameterInfo[] pInfo = method.GetParameters();
var parameterValues = new object[parameterValueStrings.Length];
for (int i = 0; i < pInfo.Length; i++)
{
parameterValues[i] = ConvertFromString(pInfo[i].ParameterType, parameterValueStrings[i]);
}
// assumes you are instantiating the target from db and that it has a parameterless constructor
// otherwise, if the target is already known to you and instantiated, just use it...
return method.Invoke(Activator.CreateInstance(targetType), parameterValues);
}
public static string ConvertToString(Type type, object val)
{
if (val is string)
{
return (string) val;
}
TypeConverter tc = TypeDescriptor.GetConverter(type);
if (tc == null)
{
throw new Exception(type.Name + " is not convertable to string");
}
return tc.ConvertToString(null, CultureInfo.InvariantCulture, val);
}
public static object ConvertFromString(Type type, string val)
{
TypeConverter tc = TypeDescriptor.GetConverter(type);
if (tc == null)
{
throw new Exception(type.Name + " is not convertable.");
}
if (!tc.IsValid(val))
{
throw new Exception(type.Name + " is not convertable from " + val);
}
return tc.ConvertFrom(null, CultureInfo.InvariantCulture, val);
}
}
}
I would think you have 2 major options:
Store the type name along with the parameter value and use that to cast things using Type.GetType(string) to resolve the type in question.
Standardize all the methods to be called this way to accept an array of strings, and expect the methods to do any necessary casting.
I know you've stated that you're not doing option 1, but it would help things from the standpoint of calling the functions.
Option 2 is the far more 'generic' way to handle the situation, assuming all values can be represented by and cast/converted from strings to the appropriate type. Of course, that only helps if you actually have control over the definition of the methods being called.
Below is a useful extension method I use in .NET 3.5.
With this extension method available, your code could look like this:
var valueInDb = GetStringFromDb().Replace("CustomerType.", string.Empty);
var value = valueInDb.ToEnum(CustomerType.Associate);
By supplying the default value in the parameter, the compiler will know which Enum you want your string to be turned into. It will try to find your text in the Enum. If it doesn't it will return the default value.
Here is the extension method: (this version also does partial matches, so even "M" would work nicely!)
public static T ToEnum<T>(this string input, T defaultValue)
{
var enumType = typeof (T);
if (!enumType.IsEnum)
{
throw new ArgumentException(enumType + " is not an enumeration.");
}
// abort if no value given
if (string.IsNullOrEmpty(input))
{
return defaultValue;
}
// see if the text is valid for this enumeration (case sensitive)
var names = Enum.GetNames(enumType);
if (Array.IndexOf(names, input) != -1)
{
// case insensitive...
return (T) Enum.Parse(enumType, input, true);
}
// do partial matching...
var match = names.Where(name => name.StartsWith(input, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
if(match != null)
{
return (T) Enum.Parse(enumType, match);
}
// didn't find one
return defaultValue;
}
I still don't fully understand your question... however, you say "Everything is fine except for the parameters."
I'll assume "CustomerType" the name of a property on your object, and "Master" is the string value you want to put in that property.
Here is (another) extension method that may help.
Once you have your new object and the value and property name from the database field, you could use this:
// string newValue = "Master";
// string propertyName = "CustomerType";
myNewObject.SetPropertyValue(propertyName, newValue)
Method:
/// <summary>Set the value of this property, as an object.</summary>
public static void SetPropertyValue(this object obj,
string propertyName,
object objValue)
{
const BindingFlags attr = BindingFlags.Public | BindingFlags.Instance;
var type = obj.GetType();
var property = type.GetProperty(propertyName, attr);
if(property == null) return;
var propertyType = property.PropertyType;
if (propertyType.IsValueType && objValue == null)
{
// This works for most value types, but not custom ones
objValue = 0;
}
// need to change some types... e.g. value may come in as a string...
var realValue = Convert.ChangeType(objValue, propertyType);
property.SetValue(obj, realValue, null);
}
If you are using .NET 4 you can do the following.
var result = default(CustomerType);
if (!Enum.TryParse("Master", out result))
{
// handle error
}