有没有办法将这两种方法合并为一个方法或重载方法?

时间:2014-01-29 01:55:31

标签: c# .net generics enums

这有点棘手。也许有人的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,则抛出异常。

但是,有时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
}

我会收到错误:已经声明了具有相同签名的成员

所以,这让我只剩下一个选项:实现一个专为可空类型设计的完全独立的方法:

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;的情况下将使用重载。并且当它不是时调用另一个重载?

7 个答案:

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