¿Existe una alternativa mejor que ésta para "encender el tipo"?

Resuelto xyz asked hace 15 años • 32 respuestas

Dado que C# no puede funcionar switchcon un tipo (que, según tengo entendido, no se agregó como un caso especial porque islas relaciones significan que podría aplicarse más de uno distinto case), ¿existe una mejor manera de simular el cambio de tipo aparte de esta?

void Foo(object o)
{
    if (o is A)
    {
        ((A)o).Hop();
    }
    else if (o is B)
    {
        ((B)o).Skip();
    }
    else
    {
        throw new ArgumentException("Unexpected type: " + o.GetType());
    }
}
xyz avatar Nov 18 '08 22:11 xyz
Aceptado

Con C# 7 , que se incluye con Visual Studio 2017 (versión 15.*), puede usar tipos en casedeclaraciones (coincidencia de patrones):

switch(shape)
{
    case Circle c:
        WriteLine($"circle with radius {c.Radius}");
        break;
    case Rectangle s when (s.Length == s.Height):
        WriteLine($"{s.Length} x {s.Height} square");
        break;
    case Rectangle r:
        WriteLine($"{r.Length} x {r.Height} rectangle");
        break;
    default:
        WriteLine("<unknown shape>");
        break;
    case null:
        throw new ArgumentNullException(nameof(shape));
}

Con C# 6, puedes usar una instrucción switch con el operador nameof() (gracias @Joey Adams):

switch(o.GetType().Name) {
    case nameof(AType):
        break;
    case nameof(BType):
        break;
}

Con C# 5 y versiones anteriores, podrías usar una instrucción de cambio, pero tendrás que usar una cadena mágica que contenga el nombre del tipo... que no es particularmente fácil de refactorizar (gracias @nukefusion)

switch(o.GetType().Name) {
  case "AType":
    break;
}
Zachary Yates avatar Nov 18 '2008 15:11 Zachary Yates

Definitivamente falta el cambio de tipos en C# ( ACTUALIZACIÓN: en C#7/VS 2017 se admite el cambio de tipos; consulte la respuesta de Zachary Yates ). Para hacer esto sin una declaración if/else if/else grande, necesitarás trabajar con una estructura diferente. Hace un tiempo escribí una publicación de blog que detalla cómo construir una estructura TypeSwitch.

https://learn.microsoft.com/archive/blogs/jaredpar/switching-on-types

Versión corta: TypeSwitch está diseñado para evitar conversiones redundantes y proporcionar una sintaxis similar a una declaración de cambio/caso normal. Por ejemplo, aquí está TypeSwitch en acción en un evento de formulario estándar de Windows

TypeSwitch.Do(
    sender,
    TypeSwitch.Case<Button>(() => textBox1.Text = "Hit a Button"),
    TypeSwitch.Case<CheckBox>(x => textBox1.Text = "Checkbox is " + x.Checked),
    TypeSwitch.Default(() => textBox1.Text = "Not sure what is hovered over"));

El código para TypeSwitch es bastante pequeño y se puede incluir fácilmente en su proyecto.

static class TypeSwitch {
    public class CaseInfo {
        public bool IsDefault { get; set; }
        public Type Target { get; set; }
        public Action<object> Action { get; set; }
    }

    public static void Do(object source, params CaseInfo[] cases) {
        var type = source.GetType();
        foreach (var entry in cases) {
            if (entry.IsDefault || entry.Target.IsAssignableFrom(type)) {
                entry.Action(source);
                break;
            }
        }
    }

    public static CaseInfo Case<T>(Action action) {
        return new CaseInfo() {
            Action = x => action(),
            Target = typeof(T)
        };
    }

    public static CaseInfo Case<T>(Action<T> action) {
        return new CaseInfo() {
            Action = (x) => action((T)x),
            Target = typeof(T)
        };
    }

    public static CaseInfo Default(Action action) {
        return new CaseInfo() {
            Action = x => action(),
            IsDefault = true
        };
    }
}
JaredPar avatar Nov 18 '2008 15:11 JaredPar

Una opción es tener un diccionario de Typea Action(o algún otro delegado). Busque la acción según el tipo y luego ejecútela. He usado esto para fábricas antes.

Jon Skeet avatar Nov 18 '2008 15:11 Jon Skeet

Con la respuesta de JaredPar en el fondo de mi cabeza, escribí una variante de su TypeSwitchclase que usa inferencia de tipos para una sintaxis mejor:

class A { string Name { get; } }
class B : A { string LongName { get; } }
class C : A { string FullName { get; } }
class X { public string ToString(IFormatProvider provider); }
class Y { public string GetIdentifier(); }

public string GetName(object value)
{
    string name = null;
    TypeSwitch.On(value)
        .Case((C x) => name = x.FullName)
        .Case((B x) => name = x.LongName)
        .Case((A x) => name = x.Name)
        .Case((X x) => name = x.ToString(CultureInfo.CurrentCulture))
        .Case((Y x) => name = x.GetIdentifier())
        .Default((x) => name = x.ToString());
    return name;
}

Tenga en cuenta que el orden de los Case()métodos es importante.


Obtenga el código completo y comentado de mi TypeSwitchclase . Esta es una versión abreviada funcional:

public static class TypeSwitch
{
    public static Switch<TSource> On<TSource>(TSource value)
    {
        return new Switch<TSource>(value);
    }

    public sealed class Switch<TSource>
    {
        private readonly TSource value;
        private bool handled = false;

        internal Switch(TSource value)
        {
            this.value = value;
        }

        public Switch<TSource> Case<TTarget>(Action<TTarget> action)
            where TTarget : TSource
        {
            if (!this.handled && this.value is TTarget)
            {
                action((TTarget) this.value);
                this.handled = true;
            }
            return this;
        }

        public void Default(Action<TSource> action)
        {
            if (!this.handled)
                action(this.value);
        }
    }
}
Daniel A.A. Pelsmaeker avatar Apr 05 '2012 08:04 Daniel A.A. Pelsmaeker

Puede utilizar la coincidencia de patrones en C# 7 o superior:

switch (foo.GetType())
{
    case var type when type == typeof(Player):
        break;
    case var type when type == typeof(Address):
        break;
    case var type when type == typeof(Department):
        break;
    case var type when type == typeof(ContactType):
        break;
    default:
        break;
}
alhpe avatar Jan 25 '2019 21:01 alhpe