views:

86

answers:

2

I am storing object values in strings e.g.,

string[] values = new string[] { "213.4", "10", "hello", "MyValue"};

is there any way to generically initialize the appropriate object types? e.g., something like

double foo1 = AwesomeFunction(values[0]);
int foo2 = AwesomeFunction(values[1]);
string foo3 = AwesomeFunction(values[2]);
MyEnum foo4 = AwesomeFunction(values[3]);

where AwesomeFunction is the function I need. The ultimate use is to intialize properties e.g.,

MyObject obj = new MyObject();
PropertyInfo info = typeof(MyObject).GetProperty("SomeProperty");
info.SetValue(obj, AwesomeFunction("20.53"), null);

The reason I need such functionality is I am storing said values in a database, and wish to read them out via a query and then initialize the corresponding properties of an object. Is this going to be possible? The entire object is not being stored in the database, just a few fields which I'd like to read & set dynamically. I know I can do it statically, however that will get tedious, hard to maintain, and prone to mistakes with numerous different fields/properties are being read.

EDIT: Bonus points if AwesomeFunction can work with custom classes which specify a constructor that takes in a string!

EDIT2: The destination type can be know via the PropertyType, in the specific case where I want to use this type of functionality. I think Enums Would be easy to parse with this e.g.,

Type destinationType = info.PropertyType;
Enum.Parse(destinationType, "MyValue");
+2  A: 

Here's a simple version:

object ConvertToAny(string input)
{
    int i;
    if (int.TryParse(input, out i))
        return i;
    double d;
    if (double.TryParse(input, out d))
        return d;
    return input;
}

It will recognize ints and doubles, but everything else is returned as a string. The problem with handling enums is that there's no way to know what enum a value belongs to and there's no way to tell whether it should be a string or not. Other problems are that it doesn't handle dates/times or decimals (how would you distinguish them from doubles?), etc.

If you're willing to change your code like this:

PropertyInfo info = typeof(MyObject).GetProperty("SomeProperty"); 
info.SetValue(obj, AwesomeFunction("20.53", info.PropertyType), null); 

Then it becomes substantially easier:

object ConvertToAny(string input, Type target)
{
    // handle common types
    if (target == typeof(int))
        return int.Parse(input);
    if (target == typeof(double)
        return double.Parse(input);
    ...
    // handle enums
    if (target.BaseType == typeof(Enum))
        return Enum.Parse(target, input);
    // handle anything with a static Parse(string) function
    var parse = target.GetMethod("Parse",
                    System.Reflection.BindingFlags.Static |
                    System.Reflection.BindingFlags.Public,
                    null, new[] { typeof(string) }, null);
    if (parse != null)
        return parse.Invoke(null, new object[] { input });
    // handle types with constructors that take a string
    var constructor = target.GetConstructor(new[] { typeof(string) });
    if (constructor != null)
        return constructor.Invoke(new object[] { input });
}
Gabe
And anyway such function (and as your does) will return `object` so OP should cast to some type: `(int)ConvertToAny("1")`. In other words (IMO) - this not possible / the task makes no sense. Only CLR, not C# can contain functions different only by returning type
abatishchev
See EDIT2 for additional info. In my specific use, I can get the destination class type via the PropertyType property. This would make parsing Enums simple. It would also make it possible to differentiate between floats/doubles/decimals/etc. However, I was hoping to avoid writing a big if/case statement for each class type (although I guess it wouldn't be **that** messy).
mrnye
myrne: Marc's answer might still be better, but I modified mine to support enums, constructors that take a string, and anything with a Parse function.
Gabe
@Gabe Yes, yours does look much neater now. Both will work, although Marc's is a bit neater imo. The only thing I am wondering now is whether either method would be significantly slower than the other. I do not think speed will be much of an issue (unless it's taking seconds rather than milliseconds) but it would be interesting to know. I will have to run some tests.
mrnye
+4  A: 

Perhaps the first thing to try is:

object value = Convert.ChangeType(text, info.PropertyType);

However, this doesn't support extensibility via custom types; if you need that, how about:

TypeConverter tc = TypeDescriptor.GetConverter(info.PropertyType);
object value = tc.ConvertFromString(null, CultureInfo.InvariantCulture, text);
info.SetValue(obj, value, null);

Or:

info.SetValue(obj, AwesomeFunction("20.53", info.PropertyType), null);

with

public object AwesomeFunction(string text, Type type) {
    TypeConverter tc = TypeDescriptor.GetConverter(type);
    return tc.ConvertFromString(null, CultureInfo.InvariantCulture, text);
}
Marc Gravell
Wow, quick reply! Looks like this code works a treat. That TypeConverter class is pretty sweet, pretty much exactly what I was after. Thanks!
mrnye
+1.PS: for OP's edit: If you have some other custom classes besides int/double/etc, you can write a corresponding `TypeConverter`. Check [here](http://msdn.microsoft.com/en-us/library/system.componentmodel.typeconverterattribute.aspx) and [here](http://msdn.microsoft.com/en-us/library/ayybcxe5.aspx).
Danny Chen
@mrnye - for info, you can write your own type-converters pretty easily - just annotate your custom types with `[TypeConverter(...)]` to tell it what you want. You can *even* change the converters for existing types if you like...
Marc Gravell
@Danny - good links, ta. If the type is outside of your control (i.e. you can't add the attribute at source), you can also use `TypeDescriptor.AddAttributes` to associate a custom converter at runtime.
Marc Gravell
@Danny @Marc Thanks, those links are good. I think I will go down this path and implement a TypeDescriptor for each of my classes (there are only a dozen or so custom classes so is a fairly painless process). Everything else is Enums or basic classes which all appear to work with this method.
mrnye