views:

451

answers:

8
+2  Q: 

Cast object to T

I'm parsing an XML file with the XmlReader class in .NET and I thought it would be smart to write a generic parse function to read different attributes generically. I came up with the following function:

private static T ReadData<T>(XmlReader reader, string value)
{
    reader.MoveToAttribute(value);
    object readData = reader.ReadContentAsObject();
    return (T)readData;
}

As I came to realise, this does not work entirely as I have planned; it throws an error with primitive types such as int or double, since a cast cannot convert from a string to a numeric type. Is there any way for my function to prevail in modified form?

+6  A: 

First check to see if it can be cast.

if (readData is T) {
    return (T)readData;
} else {
    try {
       return (T)Convert.ChangeType(readData, typeof(T));
    } catch (InvalidCastException) {
       return default(T);
    }
}
Bob
I changed the line with Convert.ChangeType to: 'return (T)Convert.ChangeType(readData, typeof(T), System.Globalization.CultureInfo.InstalledUICulture.NumberFormat) to make it work on various different cultural configurations.
Qua
+4  A: 

Have you tried Convert.ChangeType?

If the method always returns a string, which I find odd, but that's besides the point, then perhaps this changed code would do what you want:

private static T ReadData<T>(XmlReader reader, string value)
{
    reader.MoveToAttribute(value);
    object readData = reader.ReadContentAsObject();
    return (T)Convert.ChangeType(readData, typeof(T));
}
Lasse V. Karlsen
I did initially have a look at Convert.ChangeType but decided it was not useful for this operation for some wierd reason. You and Bob both provided the same answer, and I decided to go with a mix between the your answers so I avoided using try statements but still used 'return (T)readData' when possible.
Qua
+1  A: 

Add a 'class' constraint (or more detailed, like a base class or interface of your exepected T objects):

private static T ReadData<T>(XmlReader reader, string value) where T : class
{
    reader.MoveToAttribute(value);
    object readData = reader.ReadContentAsObject();
    return (T)readData;
}

or where T : IMyInterface or where T : new(), etc

Ricardo Villamil
+2  A: 

You can presumably pass-in, as a parameter, a delegate which will convert from string to T.

ChrisW
+4  A: 

You could require the type to be a reference type :

 private static T ReadData<T>(XmlReader reader, string value) where T : class
 {
     reader.MoveToAttribute(value);
     object readData = reader.ReadContentAsObject();
     return (T)readData;
 }

And then do another that uses value types and TryParse...

 private static T ReadDataV<T>(XmlReader reader, string value) where T : struct
 {
     reader.MoveToAttribute(value);
     object readData = reader.ReadContentAsObject();
     int outInt;
     if(int.TryParse(readData, out outInt))
        return outInt
     //...
 }
Tom Ritter
A: 

try

if (readData is T)
    return (T)(object)readData;
Adinochestva
A: 

Actually, the responses bring up an interesting question, which is what you want your function to do in the case of error.

Maybe it would make more sense to construct it in the form of a TryParse method that attempts to read into T, but returns false if it can't be done?

    private static bool ReadData<T>(XmlReader reader, string value, out T data)
    {
        bool result = false;
        try
        {
            reader.MoveToAttribute(value);
            object readData = reader.ReadContentAsObject();
            data = readData as T;
            if (data == null)
            {
                // see if we can convert to the requested type
                data = (T)Convert.ChangeType(readData, typeof(T));
            }
            result = (data != null);
        }
        catch (InvalidCastException) { }
        catch (Exception ex)
        {
            // add in any other exception handling here, invalid xml or whatnot
        }
        // make sure data is set to a default value
        data = (result) ? data : default(T);
        return result;
    }

edit: now that I think about it, do I really need to do the convert.changetype test? doesn't the as line already try to do that? I'm not sure that doing that additional changetype call actually accomplishes anything. Actually, it might just increase the processing overhead by generating exception. If anyone knows of a difference that makes it worth doing, please post!

genki
+1  A: 

Actually, the problem here is the use of ReadContentAsObject. Unfortunately, this method does not live up to its expectations; while it should detect the most appropirate type for the value, it actually returns a string, no matter what(this can be verified using Reflector).

However, in your specific case, you already know the type you want to cast to, therefore i would say you are using the wrong method.

Try using ReadContentAs instead, it's exactly what you need.

private static T ReadData<T>(XmlReader reader, string value)
{
    reader.MoveToAttribute(value);
    object readData = reader.ReadContentAs(typeof(T), null);
    return (T)readData;
}
baretta
Looks quite compact and elegant. However, the solution in the accepted answer makes use of ChangeType which is compatible with multiple different cultures since it accepts an IFormatProvider. Since this is a neccesity for the project I'll keep to that solution.
Qua