我有一个类如下
public class Employee
{
Guid? Id = null;
int? salary = null; // 数据库里对应的类型为SmallInt型
/// <summary>
/// 员工编号
/// </summary>
public Guid ID
{
get
{
if (Id == null)
{
return Guid.Empty;
}
return Id.Value;
}
set
{
if (value == Guid.Empty)
{
Id = null;
}
else
{
Id = value;
}
}
}
/// <summary>
/// 员工工资
/// </summary>
public int Salary
{
get
{
// 如果员工工资为空,返回int的最小值
if (salary == null)
{
return int.MinValue;
}
return salary.Value;
}
set
{
if (value == int.MinValue)
{
salary = null;
}
else
{
salary = value;
}
}
}
}
现在,我从数据库里查询出来了记录,想通过反射把这些记录的值放进员工的实体对象里,由于salary在数据库里的类型是smallInt类型的,在给实例的salary字段设置值时会报错System.ArgumentException: 类型“System.Int16”的对象无法转换为类型“System.Nullable`1[System.Int32]”。
给salary字段赋值的代码大概为FieldInfo field = ........;field.SetValue(model, value),其中model为生成的实力对象,value为DataRow["Salary"]。
下面是SetValue发生异常时的调用堆栈
at System.RuntimeType.CheckValue(Object value, Binder binder, CultureInfo cul
ture, BindingFlags invokeAttr)
at System.Reflection.RtFieldInfo.(Object obj, Object value, B
indingFlags invokeAttr, Binder binder, CultureInfo culture, Boolean doVisibility
Check, Boolean doCheckConsistency)
at System.Reflection.RtFieldInfo.SetValue(Object obj, Object value, BindingFl
ags invokeAttr, Binder binder, CultureInfo culture)
at System.Reflection.FieldInfo.SetValue(Object obj, Object value)
我们可以看出,调用FieldInfo.SetValue ,异常是出在 CheckValue 这个内部函数。这个函数是用于判断value是否可以被反射到FieldInfo对应的那个类型。这里就是检查 Int16的值是否可以被反射到Nullable<int>. 结果是不可以的。原因是这种转换并不是一个内部默认的转换,之所以Int16可以对Nullable<int> 赋值 ,其实是调用了Nullable的操作符重载进行赋值的,而不是默认赋值(如Int16给Int32赋值那样)。而这种操作符重载的调用CheckValue是无法知道的。
这里建议用属性进行反射,而不要用Field进行反射。
Employee employee = new Employee();
PropertyInfo pi = employee.GetType().GetProperty("Salary");
pi.SetValue(employee, DataRow["Salary"], null);
这样是可以的。属性的反射是会调用属性的Set语句进行复制的,而Field的反射是直接进行内存拷贝,限制会很多。具体原因需要很多篇幅来解释,这里不多解释了。
下面是微软的CheckValue的代码,楼主自己体会吧。
internal unsafe Object CheckValue(Object value, Binder binder, CultureInfo culture, BindingFlags invokeAttr)
{
// this method is used by invocation in reflection to check whether a value can be assigned to type.
if (IsInstanceOfType(value))
return value;
// if this is a ByRef get the element type and check if it's compatible
bool isByRef = IsByRef;
if (isByRef)
{
Type elementType = GetElementType();
if (elementType.IsInstanceOfType(value) || value == null)
{
// need to create an instance of the ByRef if null was provided, but only if primitive, enum or value type
return AllocateObjectForByRef(elementType.TypeHandle, value);
}
}
else if (value == null)
return value;
else if (this == s_typedRef)
// everything works for a typedref
return value;
// check the strange ones courtesy of reflection:
// - implicit cast between primitives
// - enum treated as underlying type
// - IntPtr and System.Reflection.Pointer to pointer types
bool needsSpecialCast = IsPointer || IsEnum || IsPrimitive;
if (needsSpecialCast)
{
Type valueType;
Pointer pointer = value as Pointer;
if (pointer != null)
valueType = pointer.GetPointerType();
else
valueType = value.GetType();
if (CanValueSpecialCast(valueType.TypeHandle.Value, TypeHandle.Value))
{
if (pointer != null)
return pointer.GetPointerValue();
else
return value;
}
}
if ((invokeAttr & BindingFlags.ExactBinding) == BindingFlags.ExactBinding)
throw new ArgumentException(String.Format(CultureInfo.CurrentUICulture, Environment.GetResourceString("Arg_ObjObjEx"), value.GetType(), this));
if (binder != null && binder != Type.DefaultBinder)
{
value = binder.ChangeType(value, this, culture);
if (IsInstanceOfType(value))
return value;
// if this is a ByRef get the element type and check if it's compatible
if (isByRef)
{
Type elementType = GetElementType();
if (elementType.IsInstanceOfType(value) || value == null)
return AllocateObjectForByRef(elementType.TypeHandle, value);
}
else if (value == null)
return value;
if (needsSpecialCast)
{
Type valueType;
Pointer pointer = value as Pointer;
if (pointer != null)
valueType = pointer.GetPointerType();
else
valueType = value.GetType();
if (CanValueSpecialCast(valueType.TypeHandle.Value, TypeHandle.Value))
{
if (pointer != null)
return pointer.GetPointerValue();
else
return value;
}
}
}
throw new ArgumentException(String.Format(CultureInfo.CurrentUICulture, Environment.GetResourceString("Arg_ObjObjEx"), value.GetType(), this));
}
smallint应该对应short数据类型,这里的问题是short不能转到int,因此有3个方法:
1.把Employee里的改成short?类型
2.把数据库改成int类型
3.使用field.SetValue(model, Convert.ChangeType(value, field.FieldType));