views:

116

answers:

5

I'm trying to write a generic method for fetching an XElement value in a strongly-typed fashion. Here's what I have:

public static class XElementExtensions
{
    public static XElement GetElement(this XElement xElement, string elementName)
    {
        // Calls xElement.Element(elementName) and returns that xElement (with some validation).
    }

    public static TElementType GetElementValue<TElementType>(this XElement xElement, string elementName)
    {
        XElement element = GetElement(xElement, elementName);
        try
        {
            return (TElementType)((object) element.Value); // First attempt.
        }
        catch (InvalidCastException originalException)
        {
            string exceptionMessage = string.Format("Cannot cast element value '{0}' to type '{1}'.", element.Value,
                typeof(TElementType).Name);
            throw new InvalidCastException(exceptionMessage, originalException);
        }
    }
}

As you can see on the First attempt line of GetElementValue, I'm trying to go from string -> object -> TElementType. Unfortunately, this does not work for an integer test case. When running the following test:

[Test]
public void GetElementValueShouldReturnValueOfIntegerElementAsInteger()
{
    const int expectedValue = 5;
    const string elementName = "intProp";
    var xElement = new XElement("name");
    var integerElement = new XElement(elementName) { Value = expectedValue.ToString() };
    xElement.Add(integerElement);

    int value = XElementExtensions.GetElementValue<int>(xElement, elementName);

    Assert.AreEqual(expectedValue, value, "Expected integer value was not returned from element.");
}

I get the following exception when GetElementValue<int> is called:

System.InvalidCastException : Cannot cast element value '5' to type 'Int32'.

Am I going to have to handle each casting case (or at least the numeric ones) separately?

+1  A: 

In C# you can't cast string object to Int32. For example this code produces compilation error:

    string a = "123.4";
    int x = (int) a;

Try to use Convert class or Int32.Parse and Int32.TryParse methods if you want such functionality.
And yes, you should handle numeric casting separately, if you want to cast string to int.

MAKKAM
+2  A: 

You can't do an implicit or explicit cast from String to Int32, you need use Int32's Parse or TryParse methods for this. You could probably create some nifty extension methods, e.g:

    using System;
    using System.Diagnostics;
    using System.Globalization;
    using System.Text;

    /// <summary>
    /// Provides extension methods for strings.
    /// </summary>
    public static class StringExtensions
    {
        #region Methods
        /// <summary>
        /// Converts the specified string to a <see cref="Boolean"/>
        /// </summary>
        /// <param name="string">The string to convert.</param>
        /// <returns>The specified string as a <see cref="Boolean"/>.</returns>
        public static bool AsBoolean(this string @string)
        {
            return bool.Parse(@string);
        }

        /// <summary>
        /// Converts the specified string to a <see cref="Boolean"/> using TryParse.
        /// </summary>
        /// <remarks>
        /// If the specified string cannot be parsed, the default value (if valid) or false is returned.
        /// </remarks>
        /// <param name="string">The string to convert.</param>
        /// <param name="default">The default value for if the value cannot be parsed.</param>
        /// <returns>The specified string as a <see cref="DateTime"/>.</returns>
        public static bool AsBooleanNonStrict(this string @string, bool? @default = null)
        {
            bool @bool;
            if ((!string.IsNullOrEmpty(@string)) && bool.TryParse(@string, out @bool))
                return @bool;

            if (@default.HasValue)
                return @default.Value;

            return false;
        }

        /// <summary>
        /// Converts the specified string to a <see cref="DateTime"/>
        /// </summary>
        /// <param name="string">The string to convert.</param>
        /// <returns>The specified string as a <see cref="DateTime"/>.</returns>
        public static DateTime AsDateTime(this string @string)
        {
            return DateTime.Parse(@string);
        }

        /// <summary>
        /// Converts the specified string to a <see cref="DateTime"/> using TryParse.
        /// </summary>
        /// <remarks>
        /// If the specified string cannot be parsed, <see cref="DateTime.MinValue"/> is returned.
        /// </remarks>
        /// <param name="string">The string to convert.</param>
        /// <param name="default">The default value for if the value cannot be parsed.</param>
        /// <returns>The specified string as a <see cref="DateTime"/>.</returns>
        public static DateTime AsDateTimeNonStrict(this string @string, DateTime? @default = null)
        {
            DateTime datetime;
            if ((!string.IsNullOrEmpty(@string)) && DateTime.TryParse(@string, out datetime))
                return datetime;

            if (@default.HasValue)
                return @default.Value;

            return DateTime.MinValue;
        }

        /// <summary>
        /// Converts the specified string to a <see cref="TEnum"/>
        /// </summary>
        /// <param name="string">The string to convert.</param>
        /// <returns>The specified string as a <see cref="TEnum"/>.</returns>
        public static TEnum AsEnum<TEnum>(this string @string) where TEnum : struct
        {
            return (TEnum)Enum.Parse(typeof(TEnum), @string);
        }

        /// <summary>
        /// Converts the specified string to a <see cref="TEnum"/>
        /// </summary>
        /// <param name="string">The string to convert.</param>
        /// <returns>The specified string as a <see cref="TEnum"/>.</returns>
        public static TEnum AsEnumNonStrict<TEnum>(this string @string, TEnum @default) where TEnum : struct
        {
            TEnum @enum;
            if ((!string.IsNullOrEmpty(@string)) && Enum.TryParse(@string, out @enum))
                return @enum;

            return @default;
        }

        /// <summary>
        /// Converts the specified string to a <see cref="Int32"/>
        /// </summary>
        /// <param name="string">The string to convert.</param>
        /// <returns>The specified string as a <see cref="Int32"/>.</returns>
        public static int AsInteger(this string @string)
        {
            return int.Parse(@string);
        }

        /// <summary>
        /// Converts the specified string to a <see cref="Int32"/> using TryParse.
        /// </summary>
        /// <remarks>
        /// If the specified string cannot be parsed, the default value (if valid) or 0 is returned.
        /// </remarks>
        /// <param name="string">The string to convert.</param>
        /// <param name="default">The default value for if the value cannot be parsed.</param>
        /// <returns>The specified string as a <see cref="Int32"/>.</returns>
        public static int AsIntegerNonStrict(this string @string, int? @default = null)
        {
            int @int;
            if ((!string.IsNullOrEmpty(@string)) && int.TryParse(@string, out @int))
                return @int;

            if (@default.HasValue)
                return @default.Value;

            return 0;
        }

        /// <summary>
        /// Converts the specified string to a <see cref="bool"/>
        /// </summary>
        /// <param name="string">The string to convert.</param>
        /// <returns>The specified string as a <see cref="DateTime"/>.</returns>
        public static bool? AsNullableBolean(this string @string)
        {
            bool @bool;
            if ((string.IsNullOrEmpty(@string)) || !bool.TryParse(@string, out @bool))
                return null;

            return @bool;
        }

        /// <summary>
        /// Converts the specified string to a <see cref="DateTime"/>
        /// </summary>
        /// <param name="string">The string to convert.</param>
        /// <returns>The specified string as a <see cref="DateTime"/>.</returns>
        public static DateTime? AsNullableDateTime(this string @string)
        {
            DateTime dateTime;
            if ((string.IsNullOrEmpty(@string)) || !DateTime.TryParse(@string, out dateTime))
                return null;

            return dateTime;
        }

        /// <summary>
        /// Converts the specified string to a <see cref="DateTime"/>
        /// </summary>
        /// <param name="string">The string to convert.</param>
        /// <returns>The specified string as a <see cref="DateTime"/>.</returns>
        public static TEnum? AsNullableEnum<TEnum>(this string @string) where TEnum : struct
        {
            TEnum @enum;
            if ((string.IsNullOrEmpty(@string)) || !Enum.TryParse(@string, out @enum))
                return null;

            return @enum;
        }

        /// <summary>
        /// Converts the specified string to a <see cref="Int32"/>
        /// </summary>
        /// <param name="string">The string to convert.</param>
        /// <returns>The specified string as a <see cref="Int32"/>.</returns>
        public static int? AsNullableInteger(this string @string)
        {
            int @int;
            if ((string.IsNullOrEmpty(@string)) || !int.TryParse(@string, out @int))
                return null;

            return @int;
        }
        #endregion
    }

I typically use these quite often.

Matthew Abbott
+3  A: 

You could also try the Convert.ChangeType

Convert.ChangeType(element.Value, typeof(TElementType))
Patko
This works for me. `ChangeType` returns an object still, but the implicit cast now works. Additionally, I now check for `FormatException` instead of `InvalidCastException` in the `try/catch` block. Short and simple answer.
Secret Agent Man
Upon further investigation, you can't use this with nullable types. So using this article: http://aspalliance.com/852 I wrote an extension method to handle both types.
Secret Agent Man
A: 

string also implements IConvertible, so you can do the following.

((IConvertible)mystring).ToInt32(null);

You can even throw some extension methods around it to make it cleaner.

Michael B
+1  A: 

From your code, instead of:

return (TElementType)((object) element.Value);

you'd do this:

return (TElementType) Convert.ChangeType(element.Value, typeof (T));

The only caveat here is that TElementType must implement IConvertible. However, if you're just talking about intrinsic types, they all implement that already.

For your custom types, assuming you wanted them in here, you'd have to have your own conversion.

CubanX