2010-05-21 14 views
15

Actualmente tengo una declaración switch que corre alrededor de 300 líneas impares. Sé que esto no es tan gigante como puede ser, pero estoy seguro de que hay una mejor manera de manejar esto.Mejor alternativa a la declaración de caso

La instrucción switch toma un Enum que se utiliza para determinar ciertas propiedades que pertenecen al registro. En este momento, el problema se establece en que es muy fácil dejar fuera un valor de enumeración y que no se le dará un valor, ya que no está en la declaración de cambio.

¿Existe alguna opción que se pueda usar para garantizar que se utilice cada enumeración y se le otorgue un conjunto de valores personalizado para realizar su trabajo?

EDIT:


Ejemplo de código como se pide: (.. Esto es simplista, pero muestra exactamente lo que quiero decir también una enumeración existiría con los valores por debajo)

internal void GenerateStatusLog(LogAction ActionToLog) 
{ 
    switch (ActionToLog) 
    { 
     case LogAction.None: 
      { 
       return; 
      } 
     case LogAction.LogThis: 
      { 
       ActionText = "Logging this Information"; 
       LogText = "Go for it."; 

       break; 
      } 
    } 

    // .. Do everything else 
} 
+3

¿Puede dar un ejemplo de código (limitado) que muestre el tipo de trabajo que se realiza? –

+1

Sí, necesitamos más código! Además, mire este método http://msdn.microsoft.com/en-us/library/system.enum.getnames.aspx –

+0

@Oskar Kjellin: +1 pero ¿probablemente se refiera a valores en lugar de nombres? –

Respuesta

0

Intentar usa la reflexión

  • Decora las opciones enum con atributos que tienen el valor asociado y devuelven este valor.
  • Crear clase estática de constantes y utilizar la reflexión para ENUM-opción de asignación al constante por su nombre

hope this will help

+0

Esto podría funcionar, pero será MUY lento. El uso de la reflexión en las enumeraciones no es una buena opción para el uso normal. – Venemo

+0

No hay requisitos de rendimiento en la pregunta. Las llamadas a la base de datos de red cuestan cientos de veces más, por lo que los costos de reflexión pueden ser insignificantes –

+0

Esto se ejecutará con bastante frecuencia. No creo que esto sea lo suficientemente eficiente. –

4

Bueno, ha lanzando en el caso default ... No hay editar/tiempo de compilación construir otra que eso.

Sin embargo, estrategia, visitante y otros patrones relacionados pueden ser apropiados si elige hacerlo en tiempo de ejecución.

El código de muestra ayudará a obtener la mejor respuesta.

EDIT: Gracias por la muestra. Sigo pensando que necesita un poco de adelgazamiento ya que no cubre si hay algunos parámetros que solo se aplican a algunos case s, etc.

La acción se usa a menudo como un alias para el patrón Command y el hecho de que su Enum es llamado LogAction significa que cada valor lleva consigo un comportamiento, ya sea implícito (inserte el código apropiado en un case) o explícito (en la clase de jerarquía de comandos específica).

Por lo tanto, me parece que el uso del patrón Command es apropiado (aunque su muestra no lo demuestra), es decir, tener una clase (potencialmente una jerarquía usando sobrecargas de constructor o cualquier otro [conjunto de] mecanismos de fábrica) mantiene el estado asociado con la solicitud junto con el comportamiento específico. Luego, en lugar de pasar un valor de Enum, cree una instancia LogCommand apropiada en el registrador, que solo la invoca (posiblemente pasando un "receptáculo" de Log Sink en el que el comando puede iniciar sesión). De lo contrario, estás metiendo subconjuntos aleatorios de parámetros en diferentes lugares.

relacionados seeAlso mensajes:

+0

@downvoter explicar por favor para el bien de la amistad - feliz de editar ... –

0

Algunas veces el almacenamiento de las opciones en un mapa es una buena solución, se puede externalizar la configuración en un archivo también, no estoy seguro de si se aplica a su aplicación.

5

EDITAR

pensé que esto de nuevo, miró a su alrededor en cuestiones conexas en el SO, y escribí algo de código. Creé una clase llamada AdvancedSwitch<T>, que le permite agregar casos y expone un método para evaluar un valor y le permite especificar valores que debería verificar su existencia.

Esto es lo que ocurrió:

public class AdvancedSwitch<T> where T : struct 
{ 
    protected Dictionary<T, Action> handlers = new Dictionary<T, Action>(); 

    public void AddHandler(T caseValue, Action action) 
    { 
     handlers.Add(caseValue, action); 
    } 

    public void RemoveHandler(T caseValue) 
    { 
     handlers.Remove(caseValue); 
    } 

    public void ExecuteHandler(T actualValue) 
    { 
     ExecuteHandler(actualValue, Enumerable.Empty<T>()); 
    } 

    public void ExecuteHandler(T actualValue, IEnumerable<T> ensureExistence) 
    { 
     foreach (var val in ensureExistence) 
      if (!handlers.ContainsKey(val)) 
       throw new InvalidOperationException("The case " + val.ToString() + " is not handled."); 

     handlers[actualValue](); 
    } 
} 

se puede consumir la clase de esta manera:

public enum TrafficColor { Red, Yellow, Green } 

public static void Main() 
{ 
    Console.WriteLine("Choose a traffic color: red, yellow, green?"); 
    var color = (TrafficColor)Enum.Parse(typeof(TrafficColor), Console.ReadLine()); 
    var result = string.Empty; 

    // Creating the "switch" 
    var mySwitch = new AdvancedSwitch<TrafficColor>(); 

    // Adding a single case 
    mySwitch.AddHandler(TrafficColor.Green, delegate 
    { 
     result = "You may pass."; 
    }); 

    // Adding multiple cases with the same action 
    Action redAndYellowDelegate = delegate 
    { 
     result = "You may not pass."; 
    }; 
    mySwitch.AddHandler(TrafficColor.Red, redAndYellowDelegate); 
    mySwitch.AddHandler(TrafficColor.Yellow, redAndYellowDelegate); 

    // Evaluating it 
    mySwitch.ExecuteHandler(color, (TrafficColor[])Enum.GetValues(typeof(TrafficColor))); 

    Console.WriteLine(result); 
} 

Con el uso creativo de los delegados anónimos, se puede añadir fácilmente nuevos casos a su "interruptor de bloqueo". :)
No es que también pueda usar expresiones lambda y bloques lambda, por ejemplo () => { ... } en lugar de delegate { ... }.

Puede usar esta clase fácilmente en lugar de los bloques de conmutación largos.

Original post:

Si utiliza Visual Studio, cree siempre swich declaraciones con el fragmento de código switch. Escriba switch presione la tecla dos veces y automáticamente genera todas las posibilidades para usted.

Luego, agregue una caja default al final, que arroja una excepción, de esa manera al probar su aplicación notará que hay una caja no controlada, al instante.

quiero decir algo como esto:

switch (something) 
{ 
    ... 
    case YourEnum.SomeValue: 
     ... 
     break; 
    default: 
     throw new InvalidOperationException("Default case reached."); 
} 
+0

-1 No creo que este es el problema. –

+1

@Oskar, ¿por qué no? Me parece que @Ruben describe exactamente lo mismo, pero obtuvo +2 y no -1. – Venemo

+1

@Venemo ¿por qué volver a publicarlo media hora más tarde? ;) –

2

Una posible solución es utilizar un SortedDictionary:

delegate void EnumHandler (args); 
SortedDictionary <Enum, EnumHandler> handlers; 

constructor 
{ 
    handlers = new SortedDictionary <Enum, EnumHandler>(); 
    fill in handlers 
} 

void SomeFunction (Enum enum) 
{ 
    EnumHandler handler; 

    if (handlers.TryGetValue (enum, out handler)) 
    { 
    handler (args); 
    } 
    else 
    { 
    // not handled, report an error 
    } 
} 

Este método le permiten reemplazar los controladores de forma dinámica. También podría usar una Lista como la parte de valor del diccionario y tener múltiples controladores para cada enumeración.

+0

¿Alguna razón para un _Sorted_ específicamente? –

+0

@RubenBartelink: hábito principalmente. Cuando empleo un patrón como este, la clave generalmente no es una enumeración (un tipo o una cadena, por ejemplo). Aquí, como la clave es una enumeración, puedes usar un vector. – Skizz

+0

pero 'SortedDictionary', en comparación con' Dictionary' solo le permite enumerar en orden de manera eficiente ¿no? IOW, independientemente de cuál sea su clave, ambos se apoyan en 'Equals' y' GetHashCode' de manera equivalente. Estoy de acuerdo en que un vector podría ser apropiado [suponiendo que esta pregunta tiene sentido: D] –

0

largo ejemplo de código aquí, y el código genérico final es un poco pesado (EDITAR han añadido un ejemplo adicional que elimina la necesidad de que los soportes de ángulo a expensas de cierta flexibilidad final).

Una cosa que esta solución le dará es un buen rendimiento, no tan buena como una instrucción de cambio directa, pero cada declaración de caso se convierte en una búsqueda de diccionario y una invocación de método, por lo que sigue siendo bastante buena. Sin embargo, la primera llamada recibirá una penalización de rendimiento debido al uso de un genérico estático que se refleja en la inicialización.

Crear un atributo y el tipo genérico de la siguiente manera:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] 
public class DynamicSwitchAttribute : Attribute 
{ 
public DynamicSwitchAttribute(Type enumType, params object[] targets) 
{ Targets = new HashSet<object>(targets); EnumType = enumType; } 
public Type EnumType { get; private set; } 
public HashSet<object> Targets { get; private set; } 
} 

//this builds a cache of methods for a given TTarget type, with a 
//signature equal to TAction, 
//keyed by values of the type TEnum. All methods are expected to 
//be instance methods. 
//this code can easily be modified to support static methods instead. 
//what would be nice here is if we could enforce a generic constraint 
//on TAction : Delegate, but we can't. 
public static class DynamicSwitch<TTarget, TEnum, TAction> 
{ 
//our lookup of actions against enum values. 
//note: no lock is required on this as it is built when the static 
//class is initialised. 

private static Dictionary<TEnum, TAction> _actions = 
    new Dictionary<TEnum, TAction>(); 

private static MethodInfo _tActionMethod; 
private static MethodInfo TActionMethod 
{ 
    get 
    { 
    if (_tActionMethod == null) 
    { 
    //one criticism of this approach might be that validation exceptions 
    //will be thrown inside a TypeInitializationException. 
    _tActionMethod = typeof(TAction).GetMethod("Invoke", 
     BindingFlags.Instance | BindingFlags.Public); 

    if (_tActionMethod == null) 
    throw new ArgumentException(/*elided*/); 

    //verify that the first parameter type is compatible with our 
    //TTarget type. 
    var methodParams = _tActionMethod.GetParameters(); 
    if (methodParams.Length == 0) 
    throw new ArgumentException(/*elided*/); 

    //now check that the first parameter is compatible with our type TTarget 
    if (!methodParams[0].ParameterType.IsAssignableFrom(typeof(TTarget))) 
    throw new ArgumentException(/*elided*/); 
    } 
    return _tActionMethod; 
    } 
} 

static DynamicSwitch() 
{ 
    //examine the type TTarget to extract all public instance methods 
    //(you can change this to private instance if need be) which have a 
    //DynamicSwitchAttribute defined. 
    //we then project the attributes and the method into an anonymous type 
    var possibleMatchingMethods = 
    from method in typeof(TTarget). 
     GetMethods(BindingFlags.Public | BindingFlags.Instance) 
    let attributes = method.GetCustomAttributes(
     typeof(DynamicSwitchAttribute), true). 
     Cast<DynamicSwitchAttribute>().ToArray() 
    where attributes!= null && attributes.Length == 1 
     && attributes[0].EnumType.Equals(typeof(TEnum)) 
    select new { Method = method, Attribute = attributes[0] }; 

    //create linq expression parameter expressions for each of the 
    //delegate type's parameters 
    //these can be re-used for each of the dynamic methods we generate. 
    ParameterExpression[] paramExprs = TActionMethod.GetParameters(). 
    Select((pinfo, index) => 
    Expression.Parameter(
     pinfo.ParameterType, pinfo.Name ?? string.Format("arg{0}")) 
    ).ToArray(); 
    //pre-build an array of these parameter expressions that only 
    //include the actual parameters 
    //for the method, and not the 'this' parameter. 
    ParameterExpression[] realParamExprs = paramExprs.Skip(1).ToArray(); 

    //this has to be generated for each target method. 
    MethodCallExpression methodCall = null; 

    foreach (var match in possibleMatchingMethods) 
    { 
    if (!MethodMatchesAction(match.Method)) 
    continue; 

    //right, now we're going to use System.Linq.Expressions to build 
    //a dynamic expression to invoke this method given an instance of TTarget. 
    methodCall = 
    Expression.Call(
     Expression.Convert(
     paramExprs[0], typeof(TTarget) 
     ), 
     match.Method, realParamExprs); 

    TAction dynamicDelegate = Expression. 
    Lambda<TAction>(methodCall, paramExprs).Compile(); 

    //now we have our method, we simply inject it into the dictionary, using 
    //all the unique TEnum values (from the attribute) as the keys 
    foreach (var enumValue in match.Attribute.Targets.OfType<TEnum>()) 
    { 
    if (_actions.ContainsKey(enumValue)) 
    throw new InvalidOperationException(/*elided*/); 

    _actions[enumValue] = dynamicDelegate; 
    } 
    } 
} 

private static bool MethodMatchesAction(MethodInfo method) 
{ 
    //so we want to check that the target method matches our desired 
    //delegate type (TAction). 
    //The way this is done is to fetch the delegate type's Invoke 
    //method (implicitly invoked when you invoke delegate(args)), and 
    //then we check the return type and parameters types of that 
    //against the return type and args of the method we've been passed. 

    //if the target method's return type is equal to or derived from the 
    //expected delegate's return type, then all is good. 

    if (!_tActionMethod.ReturnType.IsAssignableFrom(method.ReturnType)) 
    return false; 

    //now, the parameter lists of the method will not be equal in length, 
    //as our delegate explicitly includes the 'this' parameter, whereas 
    //instance methods do not. 

    var methodParams = method.GetParameters(); 
    var delegateParams = TActionMethod.GetParameters(); 

    for (int i = 0; i < methodParams.Length; i++) 
    { 
    if (!methodParams[i].ParameterType.IsAssignableFrom(
     delegateParams[i + 1].ParameterType)) 
    return false; 
    } 
    return true; 
} 


public static TAction Resolve(TEnum value) 
{ 
    TAction result; 

    if (!_actions.TryGetValue(value, out result)) 
    throw new ArgumentException("The value is not mapped"); 

    return result; 
} 
} 

Ahora hacer esto en una prueba de unidad:

[TestMethod] 
public void TestMethod1() 
{ 
    Assert.AreEqual(1, 
    DynamicSwitch<UnitTest1, Blah, Func<UnitTest1, int>>. 
     Resolve(Blah.BlahBlah)(this)); 

    Assert.AreEqual(125, 
    DynamicSwitch<UnitTest1, Blah, Func<UnitTest1, int>>. 
     Resolve(Blah.Blip)(this)); 

Assert.AreEqual(125, 
    DynamicSwitch<UnitTest1, Blah, Func<UnitTest1, int>>. 
     Resolve(Blah.Bop)(this)); 
} 

public enum Blah 
{ 
BlahBlah, 
Bloo, 
Blip, 
Bup, 
Bop 
} 


[DynamicSwitchAttribute(typeof(Blah), Blah.BlahBlah)] 
public int Method() 
{ 
return 1; 
} 

[DynamicSwitchAttribute(typeof(Blah), Blah.Blip, Blah.Bop)] 
public int Method2() 
{ 
return 125; 
} 

Así, dado un valor de TEnum, y su tipo preferido 'acción' (en su código parecería que simplemente no devuelve nada y modifica el estado interno de la clase), simplemente consulte la clase DynamicSwitch <>, solicítelo para resolver un método de destino y luego invocalo en línea (pasando el objeto de destino en que el método será invocado como el primer parámetro).

No estoy realmente esperando los votos para esto - es una solución MAD para ser honesto (que tiene la ventaja de poder ser aplicado para cualquier tipo de enumeración, e incluso los valores discretos de tipo int/flotador/double, además de admitir cualquier tipo de delegado) - ¡así que tal vez sea un poco mazo!

EDITAR

Una vez que tenga un genérico estático como sigue, demonios escuadra angular sobreviene - por lo que queremos tratar de deshacerse de ellos. Muchas veces, esto se hace por inferencia de tipo en parámetros de método, etc., pero tenemos un problema aquí que no podemos inferir fácilmente la firma de un delegado sin repetir la llamada al método, es decir, (args) => return.

Sin embargo, parece que necesita un método que no requiere parámetros y devuelve el vacío, por lo que puede cerrar este gigantesco genérico al fijar el tipo de delegado en Acción, y agregar una API fluida a la mezcla (si ese es su tipo de cosas):

public static class ActionSwitch 
{ 
    public class SwitchOn<TEnum> 
    { 
    private TEnum Value { get; set; } 

    internal SwitchOn(TEnum value) 
    { 
     Value = value; 
    } 

    public class Call<TTarget>{ 
     private TEnum Value { get; set; } 
     private TTarget Target { get; set; } 

     internal Call(TEnum value, TTarget target) 
     { 
     Value = value; 
     Target = target; 
     Invoke(); 
     } 

     internal void Invoke(){ 
      DynamicSwitch<TTarget, TEnum, Action<TTarget>>.Resolve(Value)(Target); 
     } 
    } 

    public Call<TTarget> On<TTarget>(TTarget target) 
    { 
     return new Call<TTarget>(Value, target); 
    } 
    } 

    public static SwitchOn<TEnum> Switch<TEnum>(TEnum onValue) 
    { 
    return new SwitchOn<TEnum>(onValue); 
    } 
} 

Ahora añadir esto al proyecto de prueba:

[TestMethod] 
public void TestMethod2() 
{ 
    //no longer have any angle brackets 
    ActionSwitch.Switch(Blah.Bup).On(this); 

    Assert.IsTrue(_actionMethod1Called); 
} 

private bool _actionMethod1Called; 

[DynamicSwitch(typeof(Blah), Blah.Bup)] 
public void ActionMethod1() 
{ 
    _actionMethod1Called = true; 
} 

el único problema con esto (aparte de la complejidad de la solución :)) es que tendría que volver -construye este tipo de envoltorio estático cada vez que quieras usar un nuevo tipo de objetivo delegar te para un cambio dinámico en otro lugar. Puede generar una versión genérica basada en la Acción < ...> y Func < ...> delegados que incorporan TArg1, TArg (n) y TReturn (si Func <>) - pero terminaría escribiendo un mucho más código.

Quizás convertiré esto en un artículo en mi blog y haré todo eso, ¡si tengo tiempo!

+0

+1 Interesante, si es tangencial. Eche un vistazo a los dos enlaces que agregué al final de mi publicación. También * tiene * para poner saltos de línea en el código si espera que las personas lo lean –

+1

Se agregaron algunos saltos de línea: tiene razón, es muy difícil leer sin él. (Es difícil de leer también, supongo) –

Cuestiones relacionadas