我做了以下扩展方法...
public static class ObjectExtensions
{
public static T As<T>(this object pObject, T pDefaultValue)
{
if (pObject == null || pObject == DBNull.Value)
return pDefaultValue;
return (T) pObject;
}
}
...我用于例如阅读这样的数据:
string field = datareader["column"].As("default value when null")
但是当我想从盒装值转换为可以为空的枚举时,它不起作用。我能想到的最好的是(凌乱的WIP代码不起作用):
public static class ObjectExtensions
{
public static T As<T>(this object pObject, T pDefaultValue)
{
if (pObject == null || pObject == DBNull.Value)
return pDefaultValue;
var lType = typeof (T);
if (!IsNullableEnum(lType))
return (T) pObject;
var lEnumType = Nullable.GetUnderlyingType(lType);
var lEnumPrimitiveType = lEnumType.GetEnumUnderlyingType();
if (lEnumPrimitiveType == typeof(int))
{
var lObject = (int?) pObject;
return (T) Convert.ChangeType(lObject, lType);
}
throw new InvalidCastException();
}
private static bool IsNullableEnum(Type pType)
{
Type lUnderlyingType = Nullable.GetUnderlyingType(pType);
return (lUnderlyingType != null) && lUnderlyingType.IsEnum;
}
}
用法:
public enum SomeEnum {Value1, Value2};
object value = 1;
var result = value.As<SomeEnum?>();
当尝试将Int32转换为可为空的枚举时,当前错误是InvalidCastException。我猜这是好的,但我不知道我怎么能做到这一点?我试图创建一个可以为空的枚举T的实例并为其赋值,但我仍然坚持如何做到这一点。
任何人都有想法或更好的方法来解决这个问题?是否有可能以通用的方式解决这个问题?我已经做了很多搜索,但我没有找到任何有用的东西。
答案 0 :(得分:3)
您可以通过调用所需的可空类型的构造函数来完成此操作。像这样:
Type t = typeof(Nullable<>).MakeGenericType(lEnumType);
var ctor = t.GetConstructor(new Type[] { lEnumType });
return (T)ctor.Invoke(new object[] { pObject });
答案 1 :(得分:1)
这里存在一个更普遍的问题,那就是您无法通过单次转换取消装箱和转换类型。 但是,关于取消装箱枚举的规则特别不一致。
(SomeEnum) (object) SomeEnum.Value1; // OK (as expected)
(SomeEnum?) (object) SomeEnum.Value1; // OK (as expected)
(SomeEnum) (object) 1; // OK (this is actually allowed)
(SomeEnum?) (object) 1; // NOPE (but then this one is not)
接受的答案中的反射代码片段实际上并未创建Nullable
我们可以通过取消装箱之外的类型转换来解决一般问题。
这可以通过先对int进行拆箱而不是对其进行转换来完成,要使用通用类型参数作为目标来实现,您需要类似Cast here描述的类。
但是,在进行了一些实验之后,我发现仅使用动态广告就具有大致相同的性能:
public static T As<T>(this object pObject, T pDefaultValue = default)
{
if (pObject == null || pObject == DBNull.Value)
{
return pDefaultValue;
}
// You can fine tune this for your application,
// for example by letting through types that have implicit conversions you want to use.
if (!typeof(T).IsValueType)
{
return (T) pObject;
}
try
{
return (T) (dynamic) pObject;
}
// By using dynamic you will get a RuntimeBinderException instead of
// an InvalidCastExeption for invalid conversions.
catch (RuntimeBinderException ex)
{
throw new InvalidCastException(ex.Message);
}
}
以下是一些基准测试,以了解将int装箱到SomeEnum的不同方式之间的性能差异:
| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|----------- |----------:|---------:|---------:|-------:|------:|------:|----------:|
| Casting | 12.07 ns | 0.004 ns | 0.003 ns | - | - | - | - |
| Reflection | 374.03 ns | 2.009 ns | 1.879 ns | 0.0267 | - | - | 112 B |
| CastTo | 16.16 ns | 0.016 ns | 0.014 ns | - | - | - | - |
| Dynamic | 17.45 ns | 0.023 ns | 0.020 ns | - | - | - | - |
此解决方案还实现了通常可以通过转换实现的所有其他转换,例如:
var charVal = (object) 'A';
charVal.As<int?>();
答案 2 :(得分:0)
根据汉斯的回答,我能够让它运作起来,如果有人对此感兴趣,那就是固定版本:
public static class ObjectExtensions
{
private static Dictionary<Type, ConstructorInfo> _NullableEnumCtor = new Dictionary<Type, ConstructorInfo>();
public static T As<T>(this object pObject)
{
return As(pObject, default(T));
}
public static T As<T>(this object pObject, T pDefaultValue)
{
if (pObject == null || pObject == DBNull.Value)
return pDefaultValue;
var lObjectType = pObject.GetType();
var lTargetType = typeof(T);
if (lObjectType == lTargetType)
return (T) pObject;
var lCtor = GetNullableEnumCtor(lTargetType);
if (lCtor == null)
return (T) pObject;
return (T)lCtor.Invoke(new[] { pObject });
}
private static ConstructorInfo GetNullableEnumCtor(Type pType)
{
if (_NullableEnumCtor.ContainsKey(pType))
return _NullableEnumCtor[pType];
var lUnderlyingType = Nullable.GetUnderlyingType(pType);
if (lUnderlyingType == null || !lUnderlyingType.IsEnum)
{
lock (_NullableEnumCtor) { _NullableEnumCtor.Add(pType, null); }
return null;
}
var lNullableType = typeof(Nullable<>).MakeGenericType(lUnderlyingType);
var lCtor = lNullableType.GetConstructor(new[] { lUnderlyingType });
lock (_NullableEnumCtor) { _NullableEnumCtor.Add(pType, lCtor); }
return lCtor;
}
}
但是可以为空的枚举的附加检查/代码会损害所有其他类型的性能。在扩展方法慢~~ 2-3之前,现在它是~10-15次。使用上面的代码执行1000000(百万)次:
拆箱:4毫秒
使用扩展方法取消装箱int:59ms(之前没有照顾可空的枚举:12ms)
拆箱到可以为空的枚举:5ms
使用扩展方法取消装箱到可以为空的枚举:3382ms
因此,看看这些数字,当性能至关重要时,这些方法不应该是首选 - 至少在将它用于可以为空的枚举时不是这样。