2014-01-29 116 views
5

这是一个棘手的问题。也许某人的C#-fu比我的优越,因为我找不到解决方案。有什么办法可以将这两种方法合并为一种方法或重载方法?

我有一个方法,它需要一个参数,该参数包含一个枚举或一个指示枚举值的字符串,并返回该枚举的一个实例。它基本上是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为空,则会抛出异常。

但是,有时objnull,我想这样做。在这种情况下,我希望能够做到:

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 
} 

我会得到错误:成员具有相同签名已经宣布

所以,这给我留下了只剩下一个方法:实现一个完全独立的方法设计为可空类型:

MyEnum? foo = ParseNullable<T>(obj); 

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?; 
} 

现在我可以把这个

我的问题:有没有办法将这两种方法结合成一种方法,根据类型参数做对,或者创建一个重载,其中一个重载将用于类型参数是可空的<>而另一个超载在不是时调用?

+0

你将无法ov使用'Nullable '版本来加载该方法,因为您没有更改参数(因此不会重载)。你只是改变返回类型。 – pickypg

+0

@pickypg - 是的,这是我的问题;我希望编译器可以重载基于泛型类型约束的方法..也许C#6 heh .. –

回答

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 
- 
- 
+0

是的,有一些相同的答案,但我喜欢这一个最好的。代码很容易阅读,底部有测试结果。 –

+0

我结束了刚刚使用的东西;两种名称不同的方法。我真的希望编译器能够区分使用哪种方法,尽管我希望通过重载方法来实现。上述方法的问题是它将在运行时使用反射挖掘类型数据,这违背了通用方法的目的。我在数据绑定代码中使用它,可能正在处理成千上万行,所以在我的情况下,性能优化使用语法。谢谢!! –

+1

@MikeChristensen不客气!顺便说一句。我真的很喜欢你决定做正确的事情并使用单独的方法的事实:) – MarcinJuraszek

1

创建一个像TryParse这样的方法,并处理返回值==假大小写,以便根据空值执行所需操作。然后,您可以实现另一个方法来包装该调用,并在返回值为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); 
1

我要去提供另一种方法...返回默认值。它是一个好主意,让代表反正没事(如果你忘了初始化等)enum SA默认值...即:

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. 
+2

然后你永远不知道该字符串是否包含默认值。另外,你不能*在.NET中给'enum'设置一个默认值,你只能选择不给它任何默认值的标签。 –

+0

枚举的默认值是其基础类型的默认值,这将是0 – Moho

+0

@JonHanna相当容易将其添加进去。另外,不给出标签使得在赋值后很难很好地检查值。这正是我明确给0标签的要点,以便它可以用作后备。 –

1

由于您实际上没有通过更改返回类型来重载,所以答案是您无法按自己的意愿进行操作。

我会添加一个重载,它需要一个单独的参数来确定参数的可用性。

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); 
+0

如果'Parse '返回null它可能是一个问题,将'null'转换为** Enum ** .. –

+0

它不能返回'null',因为它通过'false'。 – pickypg

+0

是的,有趣的想法..虽然似乎有点奇怪,我宁愿只是命名这两个函数不同的东西.. –

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); 
     } 
    } 

这应该工作,如果没有,让我知道。

1

我相信你的问题的简短答案是“否”在您问题开始时提供的示例中,您希望返回两种不同的返回类型T和T ?.这本身就需要具有不同名称的方法。

这是一个link到另一个问题,在泛型类型的可能的答案很好的答案,可能有助于澄清你的问题。

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); 
    } 
+0

即使当'T'为'Nullable <>'时,它仍然会抛出'null'异常。 – MarcinJuraszek

+0

谢谢我修复了它 – Hack

+0

是的,我正在考虑沿着这条路走下去,但是如果你最终会在运行时解决所有类型信息,它似乎真的会错过泛型方法的全部观点。但这种做法可能是我最好的选择。 –