这有点棘手。也许有人的C#-fu优于我的,因为我找不到解决方案。
我有一个方法,它接受一个参数,该参数包含枚举或表示Enum值的字符串,并返回该枚举的实例。它基本上是Enum.Parse
的实现,但是作为通用方法实现。为什么.NET Framework没有内置的功能超出了我的范围。
public static T Parse<T>(object value) where T : struct
{
if (!typeof (T).IsEnum)
throw new ArgumentException("T must be an Enum type.");
if (value == null || value == DBNull.Value)
{
throw new ArgumentException("Cannot parse enum, value is null.");
}
if (value is String)
{
return (T)Enum.Parse(typeof(T), value.ToString());
}
return (T)Enum.ToObject(typeof(T), value);
}
现在,我可以这样做:
MyEnum foo = Parse<MyEnum>(obj);
获取MyEnum
的实例。如果obj
为null,则抛出异常。
但是,有时obj
是null
,我想允许这样做。在这种情况下,我希望能够做到:
MyEnum? foo = Parse<MyEnum?>(obj);
但是,对于我的生活,我无法找到一种方法来实现这一目标。首先,即使Nullable<MyEnum>
是struct
,它也无法用作Parse<T>
的类型参数。我认为这与编译器对Nullable<>
所做的所有魔法有关,所以我不会质疑它。
您似乎无法重载该方法,只能根据T
的约束区分它。例如,如果我这样做:
public static T Parse<T>(object value) where T : new()
{
// This should be called if I pass in a Nullable, in theory
}
我会收到错误:已经声明了具有相同签名的成员
所以,这让我只剩下一个选项:实现一个专为可空类型设计的完全独立的方法:
public static T? ParseNullable<T>(object value) where T : struct
{
if (!typeof (T).IsEnum)
throw new ArgumentException("T must be an Enum type.");
if (value == null || value == DBNull.Value)
return null;
if (value is String)
return Enum.Parse(typeof (T), value.ToString()) as T?;
return Enum.ToObject(typeof (T), value) as T?;
}
我现在可以这样称呼:
MyEnum? foo = ParseNullable<T>(obj);
我的问题:有没有办法将这两种方法合并为一个方法,做正确的事情取决于类型参数,或创建重载在类型参数为Nullable&lt;&gt;的情况下将使用重载。并且当它不是时调用另一个重载?
答案 0 :(得分:3)
它需要在方法中进行几次额外的类型检查,你必须跳过泛型约束,但它绝对可能:
public static T Parse<T>(object value)
{
var isNullable = typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>);
var itemType = isNullable ? typeof(T).GetGenericArguments()[0] : typeof(T);
if (!itemType.IsEnum)
throw new ArgumentException("T must be an Enum type or Nullable<> of Enum type.");
if (value == null || value == DBNull.Value)
{
if (isNullable)
return default(T); // default(Nullable<>) is null
throw new ArgumentException("Cannot parse enum, value is null.");
}
if (value is String)
{
return (T)Enum.Parse(itemType, value.ToString());
}
return (T)Enum.ToObject(itemType, value);
}
样本用法:
var items = new object[] { "A", "B", 0, 10, null, DBNull.Value };
var results = items.Select(x => new { x, e = Parse<Test?>(x) }).ToArray();
foreach (var r in results)
Console.WriteLine("{0} - {1}", r.x, r.e.ToString());
打印
A - A
B - B
0 - A
10 - B
-
-
答案 1 :(得分:2)
为什么不删除T上的约束,并执行以下操作:
public static T Parse<T>(Object value)
{
Boolean isNullable = typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>);
if (!isNullable && !typeof(T).IsEnum)
{
throw new ArgumentException();
}
if (value == null || value == DBNull.Value)
{
throw new ArgumentException();
}
if (!(value is String))
{
return (T) Enum.ToObject(typeof (T), value);
}
if (!isNullable)
{
return (T) Enum.Parse(typeof (T), value.ToString());
}
Type underlyingType = Nullable.GetUnderlyingType(typeof(T));
try
{
return (T)Enum.Parse(underlyingType, value.ToString());
}
catch (ArgumentException)
{
return default(T);
}
}
这应该有用,如果没有,请告诉我。
答案 2 :(得分:1)
创建一个类似TryParse
的方法并处理返回值== false case以使用null值执行所需操作。然后,您可以实现另一个方法来包装该调用,并在返回值为false时返回null。 (另外,请务必使用Enum.IsDefined
,因为枚举类型的任何值都可以分配给枚举,即使它没有由枚举定义)
public static bool TryParseEnum<T>( object value, out T result ) where T : struct
{
if( !typeof( T ).IsEnum )
throw new ArgumentException( "T must be an Enum type." );
if( value == null || value == DBNull.Value )
{
result = default( T );
return false;
}
if( value is String )
{
return Enum.TryParse<T>( ( string )value, out result );
}
result = ( T )Enum.ToObject( typeof( T ), value );
return Enum.IsDefined( typeof( T ), result );
}
public static Nullable<T> ParseEnum<T>( this object value ) where T: struct
{
T retVal;
if( !TryParseEnum( value, out retVal ) )
{
return null;
}
return new Nullable<T>( retVal );
}
用法:
EnumXyz? nullableEnumValue = ParseEnum<EnumXyz>( someObject );
答案 3 :(得分:1)
我将提供另一种方法...返回默认值。最好给enum
一个默认值,无论如何都不代表任何东西(如果你忘了初始化它等)......即:
enum MyEnum {
Nothing = 0,
MeaningfulValue1,
MeaningfulValue2
// etc..
}
然后你的方法就变成了:
if (value == null || value == DBNull.Value)
return default(T);
..和呼叫网站:
var val = Parse<MyEnum>(obj);
if (val == MyEnum.Nothing)
// it was null.
答案 4 :(得分:1)
由于您实际上并没有通过更改返回类型来进行重载,因此答案是您无法执行所需的操作。
我会添加一个重载,它接受一个单独的参数来确定参数的null
- 能力。
public static T Parse<T>(object value) where T : struct
{
return (T)Parse<T>(value, false);
}
public static T? Parse<T>(object value, bool nullable) where T : struct
{
T? enumValue = null;
if ( ! typeof(T).IsEnum)
{
throw new ArgumentException("T must be an Enum type.");
}
else if (value == null || value == DBNull.Value)
{
// this is the key difference
if ( ! nullable)
{
throw new ArgumentException("Cannot parse enum, value is null.");
}
}
else if (value is string)
{
enumValue = (T)Enum.Parse(typeof(T), value.ToString());
}
else
{
enumValue = (T)Enum.ToObject(typeof(T), value);
}
return enumValue;
}
用法:
MyEnum value1 = Parse<MyEnum>("A");
// returns null
MyEnum? value2 = Parse<MyEnum>(null, true);
// throws exception
MyEnum? value2 = Parse<MyEnum>(null, false);
答案 5 :(得分:1)
我相信你问题的简短答案是“不”。在您在问题开头提供的示例中,您希望返回两种不同的返回类型,T和T?。这本身就需要具有不同名称的方法。
这是另一个问题的link,对泛型类型中的nullables有一个很好的答案,可能有助于为您澄清问题。
答案 6 :(得分:1)
如果你真的想使用一种方法,那么这个怎么样?
缺点是您必须删除where T : struct
约束。
如果你想保留约束,那么将它分成两种方法是唯一的方法。
public static T Parse<T>(object value)
{
Type underlyingType = Nullable.GetUnderlyingType(typeof(T));
bool isNullable = underlyingType != null;
if (!typeof(T).IsEnum && !isNullable)
throw new ArgumentException("T must be an Enum type.");
if (value == null || value == DBNull.Value)
{
if (isNullable)
return default(T);
throw new ArgumentNullException("value");
}
if (value is String)
return (T)Enum.Parse(underlyingType ?? typeof(T), value.ToString());
if (!value.GetType().IsValueType)
throw new ArgumentException("value must be a primitive type", "value");
return (T)Enum.ToObject(underlyingType ?? typeof(T), value);
}