【问题标题】:Convert.ChangeType() fails on Nullable TypesConvert.ChangeType() 在可空类型上失败
【发布时间】:2011-04-01 16:08:50
【问题描述】:

我想将字符串转换为对象属性值,我将其名称作为字符串。我正在尝试这样做:

string modelProperty = "Some Property Name";
string value = "SomeValue";
var property = entity.GetType().GetProperty(modelProperty);
if (property != null) {
    property.SetValue(entity, 
        Convert.ChangeType(value, property.PropertyType), null);
}

问题是当属性类型是可为空的类型时,这会失败并引发 Invalid Cast Exception。这不是无法转换值的情况 - 如果我手动执行此操作(例如 DateTime? d = Convert.ToDateTime(value);)它们将起作用我已经看到了一些类似的问题,但仍然无法让它起作用。

【问题讨论】:

  • 我将 ExecuteScalar 与 PetaPoco 4.0.3 一起使用,但由于同样的原因失败:在第 554 行返回 (T)Convert.ChangeType(val, typeof(T)) 跨度>

标签: c# .net reflection


【解决方案1】:

未经测试,但也许这样的东西会起作用:

string modelProperty = "Some Property Name";
string value = "Some Value";

var property = entity.GetType().GetProperty(modelProperty);
if (property != null)
{
    Type t = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;

    object safeValue = (value == null) ? null : Convert.ChangeType(value, t);

    property.SetValue(entity, safeValue, null);
}

【讨论】:

  • 我自己只需要那段代码。感谢 Nullable.GetUnderlyingType!当我为需要它的项目构建一个穷人的 ModelBinder 时,帮了我很多。我欠你一杯啤酒!
  • 也许用(value == null) ? null 代替(value == null) ? default(t)?
  • 似乎不适用于 uniqueidentifier 到字符串。
  • 创建safeValue 变量而不是重新分配给value 有什么特别的原因吗?
  • @threadster 您不能在“Type”类型的变量上使用默认运算符。见stackoverflow.com/questions/325426/…
【解决方案2】:

你必须获得底层类型才能做到这一点......

试试这个,我已经成功地将它与泛型一起使用:

//Coalesce to get actual property type...
Type t = property.PropertyType();
t = Nullable.GetUnderlyingType(t) ?? t;

//Coalesce to set the safe value using default(t) or the safe type.
safeValue = value == null ? default(t) : Convert.ChangeType(value, t);

我在代码中的很多地方都使用了它,一个例子是我用来以类型安全的方式转换数据库值的辅助方法:

public static T GetValue<T>(this IDataReader dr, string fieldName)
{
    object value = dr[fieldName];

    Type t = typeof(T);
    t = Nullable.GetUnderlyingType(t) ?? t;

    return (value == null || DBNull.Value.Equals(value)) ? 
        default(T) : (T)Convert.ChangeType(value, t);
}

调用使用:

string field1 = dr.GetValue<string>("field1");
int? field2 = dr.GetValue<int?>("field2");
DateTime field3 = dr.GetValue<DateTime>("field3");

我在http://www.endswithsaurus.com/2010_07_01_archive.html 写了一系列博客文章,包括这篇文章(向下滚动到附录,@JohnMacintyre 实际上发现了我的原始代码中的错误,导致我走上了与你现在相同的道路)。我有一些小的修改,因为该帖子还包括枚举类型的转换,因此如果您的属性是枚举,您仍然可以使用相同的方法调用。只需添加一行以检查枚举类型,您就可以使用以下内容参加比赛:

if (t.IsEnum)
    return (T)Enum.Parse(t, value);

通常您会进行一些错误检查或使用 TryParse 而不是 Parse,但您会明白这一点。

【讨论】:

  • 谢谢 - 我仍然缺少步骤或不理解某些内容。我正在尝试设置一个属性值,为什么我得到它所在的基础类型的对象?我也不确定如何从我的代码中获取像您这样的扩展方法。我不知道要执行什么类型的操作 value.Helper().
  • @iboeno - 抱歉,正在开会,所以我无法帮助您联系这些点。很高兴你有一个解决方案。
  • 在第一个代码块中,t是一个变量(即一个值),你怎么写default(t)?您可以将default() 与(编译时)类型(可以是泛型类型)一起使用。
【解决方案3】:

这对于一个例子来说有点长,但这是一种相对健壮的方法,它将从未知值转换为未知类型的任务分开

我有一个 TryCast 方法,它执行类似的操作,并考虑了可为空的类型。

public static bool TryCast<T>(this object value, out T result)
{
    var type = typeof (T);

    // If the type is nullable and the result should be null, set a null value.
    if (type.IsNullable() && (value == null || value == DBNull.Value))
    {
        result = default(T);
        return true;
    }

    // Convert.ChangeType fails on Nullable<T> types.  We want to try to cast to the underlying type anyway.
    var underlyingType = Nullable.GetUnderlyingType(type) ?? type;

    try
    {
        // Just one edge case you might want to handle.
        if (underlyingType == typeof(Guid))
        {
            if (value is string)
            {
                value = new Guid(value as string);
            }
            if (value is byte[])
            {
                value = new Guid(value as byte[]);
            }

            result = (T)Convert.ChangeType(value, underlyingType);
            return true;
        }

        result = (T)Convert.ChangeType(value, underlyingType);
        return true;
    }
    catch (Exception ex)
    {
        result = default(T);
        return false;
    }
}

当然 TryCast 是一个带有类型参数的方法,所以要动态调用它,你必须自己构造 MethodInfo:

var constructedMethod = typeof (ObjectExtensions)
    .GetMethod("TryCast")
    .MakeGenericMethod(property.PropertyType);

然后设置实际的属性值:

public static void SetCastedValue<T>(this PropertyInfo property, T instance, object value)
{
    if (property.DeclaringType != typeof(T))
    {
        throw new ArgumentException("property's declaring type must be equal to typeof(T).");
    }

    var constructedMethod = typeof (ObjectExtensions)
        .GetMethod("TryCast")
        .MakeGenericMethod(property.PropertyType);

    object valueToSet = null;
    var parameters = new[] {value, null};
    var tryCastSucceeded = Convert.ToBoolean(constructedMethod.Invoke(null, parameters));
    if (tryCastSucceeded)
    {
        valueToSet = parameters[1];
    }

    if (!property.CanAssignValue(valueToSet))
    {
        return;
    }
    property.SetValue(instance, valueToSet, null);
}

以及处理property.CanAssignValue的扩展方法...

public static bool CanAssignValue(this PropertyInfo p, object value)
{
    return value == null ? p.IsNullable() : p.PropertyType.IsInstanceOfType(value);
}

public static bool IsNullable(this PropertyInfo p)
{
    return p.PropertyType.IsNullable();
}

public static bool IsNullable(this Type t)
{
    return !t.IsValueType || Nullable.GetUnderlyingType(t) != null;
}

【讨论】:

    【解决方案4】:

    我也有类似的需求,LukeH 的回答为我指明了方向。我想出了这个通用函数来让它变得简单。

        public static Tout CopyValue<Tin, Tout>(Tin from, Tout toPrototype)
        {
            Type underlyingT = Nullable.GetUnderlyingType(typeof(Tout));
            if (underlyingT == null)
            { return (Tout)Convert.ChangeType(from, typeof(Tout)); }
            else
            { return (Tout)Convert.ChangeType(from, underlyingT); }
        }
    

    用法是这样的:

            NotNullableDateProperty = CopyValue(NullableDateProperty, NotNullableDateProperty);
    

    注意第二个参数只是作为一个原型来展示函数如何转换返回值,所以它实际上不必是目标属性。这意味着您也可以这样做:

            DateTime? source = new DateTime(2015, 1, 1);
            var dest = CopyValue(source, (string)null);
    

    我是这样做的,而不是使用 out,因为你不能使用属性。照原样,它可以与属性和变量一起使用。如果你愿意,你也可以创建一个重载来传递类型。

    【讨论】:

      【解决方案5】:

      即使对于 Nullable 类型,这也非常有效:

      TypeConverter conv = TypeDescriptor.GetConverter(type);
      return conv.ConvertFrom(value);
      

      为了类型安全,您还应该在调用ConvertFrom() 之前调用conv.CanConvertFrom(type) 方法。如果它返回 false,您可以回退到 ChangeType 或其他内容。

      【讨论】:

      • 很好的答案——而且非常简单!
      【解决方案6】:

      我是这样弄的

      public static List<T> Convert<T>(this ExcelWorksheet worksheet) where T : new()
          {
              var result = new List<T>();
              int colCount = worksheet.Dimension.End.Column;  //get Column Count
              int rowCount = worksheet.Dimension.End.Row;
      
              for (int row = 2; row <= rowCount; row++)
              {
                  var obj = new T();
                  for (int col = 1; col <= colCount; col++)
                  {
      
                      var value = worksheet.Cells[row, col].Value?.ToString();
                      PropertyInfo propertyInfo = obj.GetType().GetProperty(worksheet.Cells[1, col].Text);
                      propertyInfo.SetValue(obj, Convert.ChangeType(value, Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType), null);
      
                  }
                  result.Add(obj);
              }
      
              return result;
          }
      

      【讨论】:

        【解决方案7】:

        感谢@LukeH
        我改变了一点:

        public static object convertToPropType(PropertyInfo property, object value)
        {
            object cstVal = null;
            if (property != null)
            {
                Type propType = Nullable.GetUnderlyingType(property.PropertyType);
                bool isNullable = (propType != null);
                if (!isNullable) { propType = property.PropertyType; }
                bool canAttrib = (value != null || isNullable);
                if (!canAttrib) { throw new Exception("Cant attrib null on non nullable. "); }
                cstVal = (value == null || Convert.IsDBNull(value)) ? null : Convert.ChangeType(value, propType);
            }
            return cstVal;
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-07-07
          相关资源
          最近更新 更多