1123 votos

Crear método Genérico de restricción de T para una Enumeración

Estoy construyendo una función de extender el concepto que Enum.Parse

  • Permite a un valor predeterminado a ser analizada en el caso de que un valor de Enumeración no se encuentra
  • Es insensible a mayúsculas-minúsculas

Así que me escribió lo siguiente:

public static T GetEnumFromString<T>(string value, T defaultValue) where T : Enum
{
    if (string.IsNullOrEmpty(value)) return defaultValue;
    foreach (T item in Enum.GetValues(typeof(T)))
    {
        if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
    }
    return defaultValue;
}

Estoy recibiendo un Error de Restricción no puede ser de clase especial 'System.Enum'.

Bastante justo, pero hay una solución para permitir que una Enumeración Genérica, o voy a tener que imitar la función de análisis y pasar a un tipo de un atributo, que obliga a que el feo de boxeo requisito para su código.

EDITAR Todas las sugerencias a continuación han sido muy apreciada, gracias.

Se han asentado en (he dejado el bucle para mantener caso de insensibilidad - estoy utilizando esta al analizar XML)

public static class EnumUtils
{
    public static T ParseEnum<T>(string value, T defaultValue) where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum) throw new ArgumentException("T must be an enumerated type");
        if (string.IsNullOrEmpty(value)) return defaultValue;

        foreach (T item in Enum.GetValues(typeof(T)))
        {
            if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        }
        return defaultValue;
    }
}

963voto

Vivek Puntos 7254

Desde Enum Tipo implementa IConvertible interfaz, una mejor implementación debe ser algo como esto:

public T GetEnumFromString<T>(string value) where T : struct, IConvertible
{
   if (!typeof(T).IsEnum) 
   {
      throw new ArgumentException("T must be an enumerated type");
   }

   //...
}

Esto permite el paso de la aplicación de los tipos de valor IConvertible. Las posibilidades son raros.

601voto

Llego tarde para el juego, pero me lo tomé como un reto para ver cómo se podía hacer. No es posible en C# (o VB.NET), pero es en MSIL. Escribí esta pequeña cosa....

// license: http://www.apache.org/licenses/LICENSE-2.0.html
.assembly MyThing{}
.class public abstract sealed MyThing.Thing
       extends [mscorlib]System.Object
{
  .method public static !!T  GetEnumFromString<valuetype .ctor ([mscorlib]System.Enum) T>(string strValue,
                                                                                          !!T defaultValue) cil managed
  {
    .maxstack  2
    .locals init ([0] !!T temp,
                  [1] !!T return_value,
                  [2] class [mscorlib]System.Collections.IEnumerator enumerator,
                  [3] class [mscorlib]System.IDisposable disposer)
    // if(string.IsNullOrEmpty(strValue)) return defaultValue;
    ldarg strValue
    call bool [mscorlib]System.String::IsNullOrEmpty(string)
    brfalse.s HASVALUE
    br RETURNDEF         // return default it empty

    // foreach (T item in Enum.GetValues(typeof(T)))
  HASVALUE:
    // Enum.GetValues.GetEnumerator()
    ldtoken !!T
    call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
    call class [mscorlib]System.Array [mscorlib]System.Enum::GetValues(class [mscorlib]System.Type)
    callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Array::GetEnumerator() 
    stloc enumerator
    .try
    {
      CONDITION:
        ldloc enumerator
        callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
        brfalse.s LEAVE

      STATEMENTS:
        // T item = (T)Enumerator.Current
        ldloc enumerator
        callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
        unbox.any !!T
        stloc temp
        ldloca.s temp
        constrained. !!T

        // if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        callvirt instance string [mscorlib]System.Object::ToString()
        callvirt instance string [mscorlib]System.String::ToLower()
        ldarg strValue
        callvirt instance string [mscorlib]System.String::Trim()
        callvirt instance string [mscorlib]System.String::ToLower()
        callvirt instance bool [mscorlib]System.String::Equals(string)
        brfalse.s CONDITION
        ldloc temp
        stloc return_value
        leave.s RETURNVAL

      LEAVE:
        leave.s RETURNDEF
    }
    finally
    {
        // ArrayList's Enumerator may or may not inherit from IDisposable
        ldloc enumerator
        isinst [mscorlib]System.IDisposable
        stloc.s disposer
        ldloc.s disposer
        ldnull
        ceq
        brtrue.s LEAVEFINALLY
        ldloc.s disposer
        callvirt instance void [mscorlib]System.IDisposable::Dispose()
      LEAVEFINALLY:
        endfinally
    }

  RETURNDEF:
    ldarg defaultValue
    stloc return_value

  RETURNVAL:
    ldloc return_value
    ret
  }
} 

Que genera una función que tendría este aspecto, si se tratara de validez de C#:

T GetEnumFromString<T>(string valueString, T defaultValue) where T : Enum

A continuación, con el siguiente código de C#:

    using MyThing;
    // stuff...
    private enum MyEnum { Yes, No, Okay }
    static void Main(string[] args)
    {
        Thing.GetEnumFromString("No", MyEnum.Yes); // returns MyEnum.No
        Thing.GetEnumFromString("Invalid", MyEnum.Okay);  // returns MyEnum.Okay
        Thing.GetEnumFromString("AnotherInvalid", 0); // compiler error, not an Enum
    }

Desafortunadamente, esto significa tener esta parte de su código escrito en MSIL en lugar de C#, con el beneficio añadido de ser el que eres capaz de restringir este método, System.Enum. Es también una especie de lástima, porque se compila en un conjunto separado. Sin embargo, esto no significa que usted tiene que implementar esa manera.

Por la eliminación de la línea .assembly MyThing{} e invocando ilasm de la siguiente manera:

ilasm.exe /DLL /OUTPUT=MyThing.netmodule

usted obtiene un netmodule en lugar de una asamblea.

Por desgracia, VS2010 (y anteriores, obviamente) no se admite la adición de netmodule referencias, lo que significa que tendría que dejarlo en 2 asambleas separadas cuando estés en modo de depuración. La única manera que usted puede agregar como parte de su asamblea, sería ejecutar csc.exe usando la /addmodule:{files} argumento de línea de comandos. No sería demasiado doloroso en un script de MSBuild. Por supuesto, si eres valiente o estúpido, puede ejecutar csc usted manualmente cada vez. Y ciertamente se hace más complicada ya que las múltiples asambleas necesitan tener acceso a ella.

Así, se PUEDE hacer en .Net. Vale la pena el esfuerzo extra? Um, bueno, supongo que voy a dejar de decidir sobre eso.


Crédito Extra: es posible en, al menos, un idioma, además de MSIL: F#.

namespace MyThing

open System;
open System.Linq;

[<Sealed>]
[<AbstractClass>]
type MyThing() =
    static member GetEnumFromString<'T when 'T : struct and 'T : (new : unit -> 'T) and 'T :> Enum> (str : string) (defaultValue : 'T) : 'T =
        let returnValue = defaultValue
        if System.String.IsNullOrEmpty(str) then defaultValue
        else
            let values : seq<'T> = Seq.cast(System.Enum.GetValues(typedefof<'T>))
            let foundVal = values |> Seq.tryFind(fun v -> v.ToString().ToUpper().Equals(str.Trim().ToUpper()))
            if foundVal.IsNone then defaultValue else foundVal.Value

Este es un poco más fácil de mantener, ya que no hay soporte IDE para el lenguaje en Visual Studio. Sin embargo, se produce considerablemente diferentes IL (el código es muy diferente) y se basa en la FSharp.Core de biblioteca, que no se distribuye con todos (?) la versión de la .NET marco.

30voto

Yahoo Serious Puntos 964

Aunque su pregunta acerca de restricción de T para una Enumeración ha sido respondida por Vivek y los comentarios, creo que su resultante a la función ParseEnum puede ser mejorado por los comentarios y los " nuevos " desarrollos:

  • uso TEnum para mayor claridad para los usuarios
  • añadir más de interfaz-limitaciones para la restricción adicional de comprobación de
  • deje TryParse de manejar ignoreCase con el parámetro existente (introducido en VS2010/.Net 4)
  • opcionalmente utilizar el genérico default valor (introducido en VS2005/.Net 2)
  • el uso de argumentos opcionales(introducido en VS2010/.Net 4) con valores por defecto, para defaultValue y ignoreCase

se traduce en:

public static class EnumUtils
{
    public static TEnum ParseEnum<TEnum>(this string value,
                                         bool ignoreCase = true,
                                         TEnum defaultValue = default(TEnum))
        where TEnum : struct,  IComparable, IFormattable, IConvertible
    {
        if ( ! typeof(TEnum).IsEnum) { throw new ArgumentException("TEnum must be an enumerated type"); }
        if (string.IsNullOrEmpty(value)) { return defaultValue; }
        TEnum lResult;
        if (Enum.TryParse(value, ignoreCase, out lResult)) { return lResult; }
        return defaultValue;
    }
}

19voto

Nescio Puntos 12613

Usted puede limitar un parámetro de tipo genérico a ser un tipo de valor (tales como int, bool, y enum) o cualquier estructura personalizada utilizando la estructura de la restricción:

public class MyClass<T> where T : struct
{
   //...
}

18voto

Karg Puntos 585

Usted puede definir un constructor estático de la clase que se compruebe que el tipo T es una enumeración y lanzar una excepción si no lo es. Este es el método mencionado por Jeffrey Richter en su libro CLR via C#.

internal sealed class GenericTypeThatRequiresAnEnum<T> {
    static GenericTypeThatRequiresAnEnum() {
        if (!typeof(T).IsEnum) {
        throw new ArgumentException("T must be an enumerated type");
        }
    }
}

Luego en el método parse, sólo se puede utilizar Enum.Parse(typeof(T), de entrada, true) para convertir de cadena a la enumeración. El último verdadero parámetro para omitir el caso de la entrada.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X