27

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");
1
  • re "bonus points" - it would be better to define and associate a TypeConverter, and have that handle this (forwarding to the constructor). Since that is what type-converters are designed for... Commented Oct 19, 2010 at 6:52

4 Answers 4

39

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);
}
Sign up to request clarification or add additional context in comments.

6 Comments

Wow, quick reply! Looks like this code works a treat. That TypeConverter class is pretty sweet, pretty much exactly what I was after. Thanks!
+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 and here.
@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...
@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.
@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.
|
3

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 });
}

Edit: Added a missing parenthesis

4 Comments

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
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).
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.
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.
3
public T Get<T>(string val)
{
    if (!string.IsNullOrWhiteSpace(val))
        return (T) TypeDescriptor.GetConverter(typeof (T)).ConvertFromString(val);
    else 
        return default(T);
}

3 Comments

It would be nice an explanation on what this code does, instead of just pasting the code
string parse to any generic
For those who would like more than what was provided on what is going on in this answer.
1

I know this doesn't answer your question, but have you looked at Dapper micro ORM?
It's über-simple (compared to LINQ to SQL or, for that reason, Entity Framework) and does what you want.

Consider this:

public class Dog
{
    public int? Age { get; set; }
    public Guid Id { get; set; }
    public string Name { get; set; }
    public float? Weight { get; set; }    
}            

var guid = Guid.NewGuid();
var dog = connection.Query<Dog>("select * from Dogs where Id = @Id",
    new { Id = 42 }
).First();

Dapper itself is packaged in a single file and, reportedly, is used by StackOverflow team (apart from Linq to SQL).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.