416 Stimmen

Gibt es eine bessere Alternative zu diesem "Einschalttyp"?

Da C# nicht in der Lage ist switch bei einem Typ (der, wie ich annehme, nicht als Sonderfall hinzugefügt wurde, weil is Beziehungen bedeuten, dass mehr als eine bestimmte case gelten könnte), gibt es eine bessere Möglichkeit, das Einschalten des Typs zu simulieren als diese?

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

446voto

Zachary Yates Punkte 12207

Mit C# 7 die mit Visual Studio 2017 (Release 15.*) ausgeliefert wurde, können Sie Typen in case Anweisungen (Mustervergleich):

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

Mit C# 6 können Sie eine switch-Anweisung mit der nameof()-Operator (danke @Joey Adams):

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

Mit C# 5 und früher konnten Sie eine Switch-Anweisung verwenden, aber Sie müssen eine magische Zeichenfolge verwenden, die den Typnamen enthält... was nicht besonders refaktorfreundlich ist (danke @nukefusion)

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

292voto

JaredPar Punkte 699699

Das Umschalten von Typen ist in C# definitiv nicht vorhanden ( UPDATE: in C#7 / VS 2017 wird das Einschalten von Typen unterstützt - siehe die Antwort von Zachary Yates ). Um dies ohne eine große if/else-Anweisung zu erreichen, müssen Sie mit einer anderen Struktur arbeiten. Vor einiger Zeit habe ich in einem Blogbeitrag beschrieben, wie man eine TypeSwitch-Struktur aufbaut.

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

Kurzfassung: TypeSwitch wurde entwickelt, um redundantes Casting zu verhindern und eine Syntax zu bieten, die einer normalen switch/case-Anweisung ähnlich ist. Hier ein Beispiel für TypeSwitch in Aktion bei einem Standard-Windows-Formularereignis

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

Der Code für TypeSwitch ist eigentlich ziemlich klein und kann leicht in Ihr Projekt eingefügt werden.

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

105voto

Jon Skeet Punkte 1325502

Eine Möglichkeit ist die Verwendung eines Wörterbuchs von Type a Action (oder ein anderer Delegierter). Suchen Sie die Aktion anhand des Typs und führen Sie sie dann aus. Ich habe dies für Fabriken vor jetzt verwendet.

49voto

Mit JaredPar's Antwort in meinem Hinterkopf, Ich schrieb eine Variante seiner TypeSwitch Klasse, die die Typinferenz für eine schönere Syntax verwendet:

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

Beachten Sie, dass die Reihenfolge der Case() Methoden ist wichtig.


Holen Sie sich den vollständigen und kommentierten Code für meine TypeSwitch Klasse . Dies ist eine funktionierende gekürzte Version:

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

25voto

alhpe Punkte 1294

Sie können Mustervergleiche in C# 7 oder höher verwenden:

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X