/// <summary>
/// Fail-safe method that converts data into another type. See Remarks for more information.
/// </summary>
/// <param name="data">Data to convert</param>
/// <param name="targetType">Target type. Must not be <c>null</c></param>
/// <param name="newValue">New value</param>
/// <returns><c>true</c> if data was converted successfully, <c>false</c> otherwise</returns>
/// <exception cref="ArgumentNullException"><c>targetType</c> is <c>null</c> which is not expected</exception>
/// <exception cref="InvalidCastException">When failed to convert data to specified type</exception>
/// <remarks>
/// This method tries to convert data of one type to another by performing following operations in specified
/// order:
/// <list type="bullet">
/// <item>
/// <description>If <paramref name="data"/> is <c>null</c>, result is also <c>null</c>.</description>
/// </item>
/// <item>
/// <description>If <paramref name="targetType"/> is assignable from type of <paramref name="data"/>, result is the same as data.</description>
/// </item>
/// <item>
/// <description>
/// If <paramref name="targetType"/> is enumeration and type of <paramref name="data"/> is string,
/// enumeration parsing is made using <see cref="Enum.Parse"/> method.
/// </description>
/// </item>
/// <item>
/// <description>
/// If all previous conditions not met, type is converted to another type by calling <see cref="Convert.ChangeType"/> method.
/// </description>
/// </item>
/// <item>
/// <description>
/// If conversion fails with <see cref="InvalidCastException"/> exception, <paramref name="targetType"/> in
/// inspected for presence of constuctor that accepts parameter of type of <paramref name="data"/>. In case
/// such constructor is found, it is called and its result is returned. Otherwise exception is rethrown.
/// </description>
/// </item>
/// </list>
/// </remarks>
public static bool TryConvertData(object data, Type targetType, out object newValue)
{
newValue = null;
if (targetType == null)
throw new ArgumentNullException("targetType");
bool ret = false;
try
{
if (data == null)
{
newValue = null;
}
else if (targetType.IsAssignableFrom(data.GetType()))
{
newValue = data;
}
else if (targetType.IsEnum && data is string)
{
newValue = Enum.Parse(targetType, data.ToString(), true);
}
else
{
var converter = TypeDescriptor.GetConverter(targetType);
if (converter.CanConvertFrom(data.GetType()))
newValue = converter.ConvertFrom(data);
else
{
//failed to convert data, let's see if we can find a suitable constructor - our last resort
ConstructorInfo ctor = targetType.GetConstructor(new Type[] { data.GetType() });
if (ctor != null)
newValue = ctor.Invoke(new object[] { data });
else
return false;
}
}
ret = true;
}
catch { }
return ret;
}
/// <summary>
/// Sets a valud of public, non-static property or field for given object, converting data to target type
/// as needed.
/// </summary>
/// <param name="target">Object instance to set property or field value on.</param>
/// <param name="propertyName">Name of public, non-static property or field</param>
/// <param name="value">Value for property</param>
/// <param name="throwOnError">Whether throw when error occurs or continue.</param>
/// <exception cref="ArgumentNullException">When <c>target</c> is <c>null</c>. This exception is thrown regardless of throwOnError parameter value.</exception>
/// <exception cref="InvalidCastException">When value cannot be converted to target type. Thrown only when <b>throwOnError</b> is <c>true</c></exception>
/// <exception cref="InvalidOperationException">When no property or field found with specified name. Thrown only when <b>throwOnError</b> is <c>true</c></exception>
public static void SetPropertyValue(object target, string propertyName, object value, bool throwOnError)
{
if (target == null)
{
throw new ArgumentNullException("target");
}
Type type = target.GetType();
//check if property with such name exists in target type
Type memberType = null;
PropertyInfo propertyInfo = type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public);
if (propertyInfo != null && propertyInfo.CanWrite)
memberType = propertyInfo.PropertyType;
//if no such property, we check for public field
if (memberType == null)
{
FieldInfo fieldInfo = type.GetField(propertyName, BindingFlags.Instance | BindingFlags.Public);
if (fieldInfo != null)
memberType = fieldInfo.FieldType;
}
//if we couldn't find exact match, maybe try case-insensitive?
if (memberType == null)
{
propertyInfo = type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
if (propertyInfo != null && propertyInfo.CanWrite)
{
memberType = propertyInfo.PropertyType;
propertyName = propertyInfo.Name;
}
//if no such property, we check for public field
if (memberType == null)
{
FieldInfo fieldInfo = type.GetField(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
if (fieldInfo != null)
{
memberType = fieldInfo.FieldType;
propertyName = fieldInfo.Name;
}
}
}
if (memberType != null)
{
object targetValue = null;
if (TryConvertData(value, memberType, out targetValue))
{
type.InvokeMember(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty | BindingFlags.SetField, null,
target, new object[] { targetValue });
}
else if (throwOnError)
{
throw new InvalidCastException(String.Format("Cannot convert from '{0}' to '{1}'", value.GetType().FullName, memberType.FullName));
}
}
else if (throwOnError)
{
throw new InvalidOperationException(String.Format("Property or field '{0}' not found on type '{1}'", propertyName, type.FullName));
}
}