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

7voto

Edward Ned Harvey Punkte 5630

Für eingebaute Typen können Sie die TypeCode-Aufzählung verwenden. Bitte beachten Sie, dass GetType() etwas langsam ist, aber wahrscheinlich in den meisten Situationen nicht relevant ist.

switch (Type.GetTypeCode(someObject.GetType()))
{
    case TypeCode.Boolean:
        break;
    case TypeCode.Byte:
        break;
    case TypeCode.Char:
        break;
}

Für benutzerdefinierte Typen können Sie Ihre eigene Aufzählung und entweder eine Schnittstelle oder eine Basisklasse mit abstrakten Eigenschaften oder Methoden erstellen...

Abstrakte Klasse Implementierung der Eigenschaft

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public abstract class Foo
{
    public abstract FooTypes FooType { get; }
}
public class FooFighter : Foo
{
    public override FooTypes FooType { get { return FooTypes.FooFighter; } }
}

Implementierung der Methode der abstrakten Klasse

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public abstract class Foo
{
    public abstract FooTypes GetFooType();
}
public class FooFighter : Foo
{
    public override FooTypes GetFooType() { return FooTypes.FooFighter; }
}

Interface-Implementierung der Eigenschaft

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public interface IFooType
{
    FooTypes FooType { get; }
}
public class FooFighter : IFooType
{
    public FooTypes FooType { get { return FooTypes.FooFighter; } }
}

Interface-Implementierung der Methode

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public interface IFooType
{
    FooTypes GetFooType();
}
public class FooFighter : IFooType
{
    public FooTypes GetFooType() { return FooTypes.FooFighter; }
}

Einer meiner Kollegen hat mir auch gerade davon erzählt: Das hat den Vorteil, dass man es für buchstäblich alle Arten von Objekten verwenden kann, nicht nur für solche, die man selbst definiert. Es hat den Nachteil, dass es ein bisschen größer und langsamer ist.

Definieren Sie zunächst eine statische Klasse wie diese:

public static class TypeEnumerator
{
    public class TypeEnumeratorException : Exception
    {
        public Type unknownType { get; private set; }
        public TypeEnumeratorException(Type unknownType) : base()
        {
            this.unknownType = unknownType;
        }
    }
    public enum TypeEnumeratorTypes { _int, _string, _Foo, _TcpClient, };
    private static Dictionary<Type, TypeEnumeratorTypes> typeDict;
    static TypeEnumerator()
    {
        typeDict = new Dictionary<Type, TypeEnumeratorTypes>();
        typeDict[typeof(int)] = TypeEnumeratorTypes._int;
        typeDict[typeof(string)] = TypeEnumeratorTypes._string;
        typeDict[typeof(Foo)] = TypeEnumeratorTypes._Foo;
        typeDict[typeof(System.Net.Sockets.TcpClient)] = TypeEnumeratorTypes._TcpClient;
    }
    /// <summary>
    /// Throws NullReferenceException and TypeEnumeratorException</summary>
    /// <exception cref="System.NullReferenceException">NullReferenceException</exception>
    /// <exception cref="MyProject.TypeEnumerator.TypeEnumeratorException">TypeEnumeratorException</exception>
    public static TypeEnumeratorTypes EnumerateType(object theObject)
    {
        try
        {
            return typeDict[theObject.GetType()];
        }
        catch (KeyNotFoundException)
        {
            throw new TypeEnumeratorException(theObject.GetType());
        }
    }
}

Und dann können Sie es so verwenden:

switch (TypeEnumerator.EnumerateType(someObject))
{
    case TypeEnumerator.TypeEnumeratorTypes._int:
        break;
    case TypeEnumerator.TypeEnumeratorTypes._string:
        break;
}

6voto

scobi Punkte 13904

Ich mochte Virtlink's Verwendung der impliziten Typisierung um die Umschaltung besser lesbar zu machen, aber mir gefiel nicht, dass ein Early-Out nicht möglich ist und dass wir Allokationen vornehmen. Drehen wir die Perf ein wenig auf.

public static class TypeSwitch
{
    public static void On<TV, T1>(TV value, Action<T1> action1)
        where T1 : TV
    {
        if (value is T1) action1((T1)value);
    }

    public static void On<TV, T1, T2>(TV value, Action<T1> action1, Action<T2> action2)
        where T1 : TV where T2 : TV
    {
        if (value is T1) action1((T1)value);
        else if (value is T2) action2((T2)value);
    }

    public static void On<TV, T1, T2, T3>(TV value, Action<T1> action1, Action<T2> action2, Action<T3> action3)
        where T1 : TV where T2 : TV where T3 : TV
    {
        if (value is T1) action1((T1)value);
        else if (value is T2) action2((T2)value);
        else if (value is T3) action3((T3)value);
    }

    // ... etc.
}

Da tun mir ja die Finger weh. Lass es uns in T4 machen:

<#@ template debug="false" hostSpecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ Assembly Name="System.Core.dll" #>
<#@ import namespace="System.Linq" #> 
<#@ import namespace="System.IO" #> 
<#
    string GenWarning = "// THIS FILE IS GENERATED FROM " + Path.GetFileName(Host.TemplateFile) + " - ANY HAND EDITS WILL BE LOST!";
    const int MaxCases = 15;
#>
<#=GenWarning#>

using System;

public static class TypeSwitch
{
<# for(int icase = 1; icase <= MaxCases; ++icase) {
    var types = string.Join(", ", Enumerable.Range(1, icase).Select(i => "T" + i));
    var actions = string.Join(", ", Enumerable.Range(1, icase).Select(i => string.Format("Action<T{0}> action{0}", i)));
    var wheres = string.Join(" ", Enumerable.Range(1, icase).Select(i => string.Format("where T{0} : TV", i)));
#>
    <#=GenWarning#>

    public static void On<TV, <#=types#>>(TV value, <#=actions#>)
        <#=wheres#>
    {
        if (value is T1) action1((T1)value);
<# for(int i = 2; i <= icase; ++i) { #>
        else if (value is T<#=i#>) action<#=i#>((T<#=i#>)value);
<#}#>
    }

<#}#>
    <#=GenWarning#>
}

Wir passen das Beispiel von Virtlink ein wenig an:

TypeSwitch.On(operand,
    (C x) => name = x.FullName,
    (B x) => name = x.LongName,
    (A x) => name = x.Name,
    (X x) => name = x.ToString(CultureInfo.CurrentCulture),
    (Y x) => name = x.GetIdentifier(),
    (object x) => name = x.ToString());

Gut lesbar und schnell. Nun, wie alle in ihren Antworten immer wieder betonen, und angesichts der Art dieser Frage, ist die Reihenfolge bei der Typisierung wichtig. Deshalb:

  • Zuerst die Blatttypen, dann die Basistypen.
  • Bei Peer-Typen sollten Sie die wahrscheinlichsten Übereinstimmungen zuerst angeben, um die Leistung zu maximieren.
  • Dies bedeutet, dass es keinen speziellen Standardfall geben muss. Verwenden Sie stattdessen einfach den Basis-Typ im Lambda und setzen Sie ihn an die letzte Stelle.

5voto

Sollte funktionieren mit

case type _:

mögen:

int i = 1;
bool b = true;
double d = 1.1;
object o = i; // whatever you want

switch (o)
{
    case int _:
        Answer.Content = "You got the int";
        break;
    case double _:
        Answer.Content = "You got the double";
        break;
    case bool _:
        Answer.Content = "You got the bool";
        break;
}

5voto

Evren Kuzucuoglu Punkte 3563

Angesichts der Tatsache, dass die Vererbung es ermöglicht, ein Objekt als mehr als einen Typ zu erkennen, denke ich, dass ein Wechsel zu einer schlechten Zweideutigkeit führen könnte. Zum Beispiel:

Fall 1

{
  string s = "a";
  if (s is string) Print("Foo");
  else if (s is object) Print("Bar");
}

Fall 2

{
  string s = "a";
  if (s is object) Print("Foo");
  else if (s is string) Print("Bar");
}

Weil s eine Zeichenkette ist et ein Objekt. Ich denke, wenn Sie ein switch(foo) erwarten Sie, dass foo mit einem und nur einem der case Erklärungen. Bei einem switch on types kann die Reihenfolge, in der Sie Ihre case-Anweisungen schreiben, möglicherweise das Ergebnis der gesamten switch-Anweisung verändern. Ich denke, das wäre falsch.

Man könnte an eine Compiler-Prüfung der Typen einer "typeswitch"-Anweisung denken, die prüft, dass die aufgezählten Typen nicht voneinander erben. Das gibt es aber nicht.

foo is T ist nicht dasselbe wie foo.GetType() == typeof(T) !!

5voto

Davide Cannizzo Punkte 2364

Gemäß der C# 7.0 Spezifikation können Sie eine lokale Variable in einer case eines switch :

object a = "Hello world";
switch (a)
{
    case string myString:
        // The variable 'a' is a string!
        break;
    case int myInt:
        // The variable 'a' is an int!
        break;
    case Foo myFoo:
        // The variable 'a' is of type Foo!
        break;
}

Dies ist der beste Weg, um so etwas zu tun, weil es nur Casting und Push-on-the-Stack-Operationen beinhaltet, die die schnellsten Operationen sind, die ein Interpreter ausführen kann, gleich nach bitweisen Operationen und boolean Bedingungen.

Der Vergleich mit einem Dictionary<K, V> Die Speicherung eines Wörterbuchs erfordert mehr Platz im Arbeitsspeicher und einige Berechnungen durch die CPU, um zwei Arrays zu erstellen (eines für die Schlüssel und das andere für die Werte) und Hash-Codes für die Schlüssel zu sammeln, um die Werte den jeweiligen Schlüsseln zuzuordnen.

Soweit ich weiß, gibt es also keinen schnelleren Weg, es sei denn, man möchte nur eine if - then - else Block mit dem is Operator wie folgt:

object a = "Hello world";
if (a is string)
{
    // The variable 'a' is a string!
} else if (a is int)
{
    // The variable 'a' is an int!
} // etc.

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