webentwicklung-frage-antwort-db.com.de

Gibt es eine bessere Alternative als diese Option, um den Typ einzuschalten?

Wenn C # nicht switch für einen Type verwenden kann (was ich nicht als Spezialfall hinzugefügt habe, da is-a-Beziehungen bedeuten, dass mehr als ein eindeutiger case zutreffen könnte), gibt es einen besseren Weg, um die Einschaltart als diese zu simulieren?

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

Das Umschalten von Typen fehlt definitiv in C # (UPDATE: In C # 7/VS 2017 wird das Einschalten von Typen unterstützt - siehe Antwort von Zachary Yates unten). Um dies ohne eine große if/else if/else-Anweisung zu tun, müssen Sie mit einer anderen Struktur arbeiten. Ich habe vor einiger Zeit einen Blogbeitrag geschrieben, in dem beschrieben wird, wie eine TypeSwitch-Struktur erstellt wird.

http://blogs.msdn.com/jaredpar/archive/2008/05/16/switching-on-types.aspx

Kurzversion: TypeSwitch soll redundantes Casting verhindern und eine Syntax geben, die einer normalen switch/case-Anweisung ähnelt. Zum Beispiel ist hier TypeSwitch für ein Standard-Windows-Formularereignis in Aktion

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

Mit C # 7 , das im Lieferumfang von Visual Studio 2017 (Release 15. *) enthalten ist, können Sie Types in case-Anweisungen verwenden (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 dem Operator nameof () (danke @Joey Adams) verwenden:

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

Mit C # 5 und früheren Versionen könnten Sie eine switch-Anweisung verwenden, aber Sie müssen eine magische Zeichenfolge verwenden, die den Typnamen enthält.

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

Eine Möglichkeit ist, ein Wörterbuch von Type bis Action (oder einem anderen Delegierten) zu haben. Suchen Sie die Aktion anhand des Typs und führen Sie sie dann aus. Ich habe das schon früher für Fabriken verwendet.

98
Jon Skeet

Mit JaredPars Antwort im Hinterkopf, schrieb ich eine Variante seiner TypeSwitch-Klasse, 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 wichtig ist.


Den vollständigen und kommentierten Code für meine TypeSwitch-Klasse abrufen . Dies ist eine funktionierende abgekü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);
        }
    }
}
47

Erstellen Sie eine Superklasse (S) und machen Sie A und B zum Erben. Dann deklarieren Sie eine abstrakte Methode für S, die jede Unterklasse implementieren muss.

Auf diese Weise kann die "foo" -Methode auch ihre Signatur in Foo (S o) ändern, wodurch der Typ sicher ist, und Sie müssen diese hässliche Ausnahme nicht auslösen.

14
Pablo Fernandez

Wenn Sie C # 4 verwenden, können Sie die neue dynamische Funktionalität nutzen, um eine interessante Alternative zu erreichen. Ich sage nicht, dass dies besser ist, es scheint sehr wahrscheinlich, dass es langsamer ist, aber es hat eine gewisse Eleganz. 

class Thing
{

  void Foo(A a)
  {
     a.Hop();
  }

  void Foo(B b)
  {
     b.Skip();
  }

}

Und die Verwendung:

object aOrB = Get_AOrB();
Thing t = GetThing();
((dynamic)t).Foo(aorB);

Der Grund dafür ist, dass der Aufruf einer dynamischen C # 4-Methode zur Laufzeit und nicht zur Kompilierzeit aufgelöst wird. Ich habe etwas mehr über diese Idee geschrieben vor kurzem . Ich möchte nochmals darauf hinweisen, dass dies wahrscheinlich schlechter ist als alle anderen Vorschläge. Ich biete es einfach als Neugier an.

8
Paul Batum

Sie sollten Ihre Methode wirklich überladen und nicht versuchen, die Disambiguierung selbst durchzuführen. Bei den meisten Antworten wurden zukünftige Unterklassen nicht berücksichtigt, was später zu wirklich schrecklichen Wartungsproblemen führen kann.

7
sep332

Für integrierte Typen können Sie die TypeCode-Enumeration verwenden. Bitte beachten Sie, dass GetType () langsam ist, aber in den meisten Situationen wahrscheinlich 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 eine eigene Aufzählung und entweder eine Schnittstelle oder eine Basisklasse mit abstrakten Eigenschaften oder Methoden erstellen.

Abstrakte Klassenimplementierung von Immobilien

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

Abstrakte Klassenimplementierung der Methode

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

Schnittstellenimplementierung von Immobilien

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

Schnittstellenimplementierung 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 eben auch davon erzählt: Dies hat den Vorteil, dass Sie es buchstäblich für alle Arten von Objekten verwenden können, nicht nur für die Objekte, die Sie definieren. Es hat den Nachteil, dass es etwas größer und langsamer ist.

Definieren Sie zuerst eine statische Klasse wie folgt:

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 kannst du es so benutzen:

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

Ich mochte Virtlinks Verwendung impliziter Schreibvorgänge , um die Umstellung deutlich lesbarer zu machen, aber ich mochte es nicht, dass ein Frühausstieg nicht möglich ist und dass wir Allokationen vornehmen. Lassen Sie uns die Perf ein wenig aufdrehen.

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

Nun, meine Finger tun weh. Lass es uns in T4 tun:

<#@ 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#>
}

Das Beispiel von Virtlink etwas anpassen:

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

Lesbar und schnell. Nun, da jeder immer wieder in seinen Antworten darauf hinweist, und aufgrund der Natur dieser Frage ist die Reihenfolge beim Typ-Matching wichtig. Deshalb:

  • Legen Sie zuerst Blattarten ein, Basistypen später.
  • Setzen Sie bei Peer-Typen zuerst die wahrscheinlichen Übereinstimmungen, um die Leistung zu maximieren.
  • Dies bedeutet, dass kein Sonderfall erforderlich ist. Verwenden Sie stattdessen einfach den untersten Typ im Lambda und setzen Sie ihn als letztes.
6
scobi

Angesichts der Vererbung kann ein Objekt als mehr als ein Typ erkannt werden. Ich denke, ein Wechsel könnte zu einer schlechten Mehrdeutigkeit führen. 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 und ein Objekt . Ich denke, wenn Sie eine switch(foo) schreiben, erwarten Sie, dass foo mit einer und nur einer der case-Anweisungen übereinstimmt. Bei einem Wechsel von Typen kann die Reihenfolge, in der Sie Ihre case-Anweisungen schreiben, möglicherweise das Ergebnis der gesamten switch-Anweisung ändern. Ich denke das wäre falsch.

Sie könnten sich eine Compiler-Prüfung der Typen einer "typeswitch" -Anweisung vorstellen, um zu prüfen, ob die aufgezählten Typen nicht voneinander erben. Das gibt es aber nicht.

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

5

Ich würde entweder

  • verwenden Sie Methodenüberladung (genau wie x0n ) oder
  • verwenden Sie Unterklassen (genau wie Pablo ) oder
  • wende das Besuchermuster an .
4
Jonas Kongslund

Ja, dank C # 7 kann dies erreicht werden, wie es gemacht wird (mit Ausdrucksmuster ):

        switch(o)
        {
            case A a:
                a.Hop();
                break;
            case B b:
                b.Skip();
                break;
            case C _: 
                return new ArgumentException("Type C will be supported in the next version");
            default:
                return new ArgumentException("Unexpected type: " + o.GetType());
        }
4
Serge Intern

Ich habe mir hier ein paar Optionen angeschaut und dabei gespiegelt, was F # kann. F # bietet eine viel bessere Unterstützung für das typbasierte Umschalten (obwohl ich mich immer noch an C # ;-p halte) .. _. Vielleicht möchten Sie hier und hier sehen.

3
Marc Gravell

Eine andere Möglichkeit wäre, eine Schnittstelle ITching zu definieren und diese dann in beiden Klassen zu implementieren. Hier ist der Sniper:

public interface IThing
{
    void Move();
}

public class ThingA : IThing
{
    public void Move()
    {
        Hop();
    }

    public void Hop(){  
        //Implementation of Hop 
    }

}

public class ThingA : IThing
{
    public void Move()
    {
        Skip();
    }

    public void Skip(){ 
        //Implementation of Skip    
    }

}

public class Foo
{
    static void Main(String[] args)
    {

    }

    private void Foo(IThing a)
    {
        a.Move();
    }
}
3
jgarcia

In solchen Fällen ende ich normalerweise mit einer Liste von Prädikaten und Aktionen. Etwas in diese Richtung:

class Mine {
  static List<Func<object, bool>> predicates;
  static List<Action<object>> actions;

  static Mine() {
    AddAction<A>(o => o.Hop());
    AddAction<B>(o => o.Skip());
  }

  static void AddAction<T>(Action<T> action) {
    predicates.Add(o => o is T);
    actions.Add(o => action((T)o);
  }

  static void RunAction(object o) {
    for (int i=0; o < predicates.Count; i++) {
      if (predicates[i](o)) {
        actions[i](o);
        break;
      }
    }
  }

  void Foo(object o) {
    RunAction(o);
  }
}
2
Hallgrim

Verwenden Sie C # 7 und Mustervergleich.

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

Erstellen Sie eine Schnittstelle, die IFooable ist, und erstellen Sie dann eine A- und B-Klasse, um eine allgemeine Methode zu implementieren, die wiederum die entsprechende Methode aufruft:

interface IFooable
{
   public void Foo();
}

class A : IFooable
{
   //other methods ...

   public void Foo()
   {
      this.Hop();
   }
}

class B : IFooable
{
   //other methods ...

   public void Foo()
   {
      this.Skip();
   }
}

class ProcessingClass
{
public void Foo(object o)
{
   if (o == null)
      throw new NullRefferenceException("Null reference", "o");

   IFooable f = o as IFooable;
   if (f != null)
   {
       f.Foo();
   }
   else
   {
       throw new ArgumentException("Unexpected type: " + o.GetType());
   }
}
}

Beachten Sie, dass es besser ist, "as" zu verwenden, statt zuerst mit "is" zu prüfen und dann zu gießen. Auf diese Weise erstellen Sie 2 Güsse (teuer).

2
Sunny Milenov

Sie können überladene Methoden erstellen:

void Foo(A a) 
{ 
   a.Hop(); 
}

void Foo(B b) 
{ 
   b.Skip(); 
}

void Foo(object o) 
{ 
   throw new ArgumentException("Unexpected type: " + o.GetType()); 
}

Verwenden Sie dynamic parameter type, um die Überprüfung der statischen Typen zu umgehen:

Foo((dynamic)something);
2

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

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

Gelegentlich fragen Sie nach, warum die Variable als string _ deklariert ist. Warum der Unterstrich? 

Nun, eine weitere Funktion, die mit C # 7.0 eingeführt wurde, besteht darin, dass Sie eine solche Variable nennen können, auf die Sie nie verweisen. Sie können also nicht auf die Variable _ verweisen. In vielen Szenarien ist es gut, dass das OP danach gefragt hat, da er nur den Typ überprüfen möchte und nicht auch eine gegossene Referenz erhalten soll. Andernfalls können Sie diese Variable umbenennen und als Referenz nach Belieben verwenden.


Dies ist der beste Weg, dies zu tun, da dies nur Casting- und Push-on-the-Stack-Operationen umfasst. Dies sind die schnellsten Operationen, die ein Interpreter unmittelbar nach bitweisen Operationen und boolean-Bedingungen ausführen kann. 

Wenn Sie dies mit einem Dictionary<K, V> vergleichen, wird hier weniger Speicherplatz benötigt: Das Halten eines Wörterbuchs erfordert mehr Platz im RAM und einige Berechnungen durch die CPU, um zwei Arrays (eines für Schlüssel und das andere für Werte) zu erstellen und Hash-Codes zu sammeln für die Schlüssel, um Werte zu ihren jeweiligen Schlüsseln zu setzen. 

Soweit ich weiß, glaube ich nicht, dass es einen schnelleren Weg geben könnte, selbst wenn Sie nicht einfach einen 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.
1
Davide Cannizzo

Sie suchen nach Discriminated Unions, einem Sprachfeature von F #, aber Sie können einen ähnlichen Effekt erzielen, indem Sie eine Bibliothek verwenden, die ich als OneOf erstellt habe 

https://github.com/mcintyre321/OneOf

Der Hauptvorteil gegenüber switch (und if und exceptions as control flow) besteht darin, dass es während der Kompilierung sicher ist - es gibt keinen Standardhandler oder einen Durchfall

void Foo(OneOf<A, B> o)
{
    o.Switch(
        a => a.Hop(),
        b => b.Skip()
    );
}

Wenn Sie o ein drittes Element hinzufügen, wird ein Compiler-Fehler angezeigt, da Sie innerhalb des Switch-Aufrufs eine Handler-Func hinzufügen müssen.

Sie können auch einen .Match ausführen, der einen Wert zurückgibt, anstatt eine Anweisung auszuführen:

double Area(OneOf<Square, Circle> o)
{
    return o.Match(
        square => square.Length * square.Length,
        circle => Math.PI * circle.Radius * circle.Radius
    );
}
1
mcintyre321

Wie Pablo vorschlägt, ist der Schnittstellenansatz fast immer das Richtige, um damit umzugehen. Um den Schalter wirklich zu nutzen, besteht eine andere Alternative darin, eine benutzerdefinierte Enumeration zu haben, die Ihren Typ in Ihren Klassen angibt.

enum ObjectType { A, B, Default }

interface IIdentifiable
{
    ObjectType Type { get; };
}
class A : IIdentifiable
{
    public ObjectType Type { get { return ObjectType.A; } }
}

class B : IIdentifiable
{
    public ObjectType Type { get { return ObjectType.B; } }
}

void Foo(IIdentifiable o)
{
    switch (o.Type)
    {
        case ObjectType.A:
        case ObjectType.B:
        //......
    }
}

Dies wird auch in BCL implementiert. Ein Beispiel ist MemberInfo.MemberTypes , ein anderes ist GetTypeCode für primitive Typen wie:

void Foo(object o)
{
    switch (Type.GetTypeCode(o.GetType())) // for IConvertible, just o.GetTypeCode()
    {
        case TypeCode.Int16:
        case TypeCode.Int32:
        //etc ......
    }
}
0
nawfal

Wenn Sie die erwartete Klasse kennen, aber noch kein Objekt haben, können Sie dies sogar tun:

private string GetAcceptButtonText<T>() where T : BaseClass, new()
{
    switch (new T())
    {
        case BaseClassReview _: return "Review";
        case BaseClassValidate _: return "Validate";
        case BaseClassAcknowledge _: return "Acknowledge";
        default: return "Accept";
    }
}
0
Chan

Ich stimme mit Jon überein, dass es einen Hash von Aktionen zum Klassennamen gibt. Wenn Sie Ihr Muster beibehalten, sollten Sie stattdessen das "as" -Konstrukt verwenden:

A a = o as A;
if (a != null) {
    a.Hop();
    return;
}
B b = o as B;
if (b != null) {
    b.Skip();
    return;
}
throw new ArgumentException("...");

Der Unterschied ist der, wenn Sie das Muster verwenden, wenn (foo ist Bar) {((Bar) foo) .Action (); } Du machst den Typ zweimal. Vielleicht optimiert der Compiler diese Arbeit nur einmal - aber ich würde nicht darauf zählen.

0
plinth

Ich würde eine Schnittstelle mit einem beliebigen Namen und Methodennamen erstellen, der für Ihren Switch sinnvoll ist. Lassen Sie uns sie jeweils anrufen: IDoable, die die Implementierung von void Do() angibt.

public interface IDoable
{
    void Do();
}

public class A : IDoable
{
    public void Hop() 
    {
        // ...
    }

    public void Do()
    {
        Hop();
    }
}

public class B : IDoable
{
    public void Skip() 
    {
        // ...
    }

    public void Do()
    {
        Skip();
    }
}

und ändern Sie die Methode wie folgt: 

void Foo<T>(T obj)
    where T : IDoable
{
    // ...
    obj.Do();
    // ...
}

Zumindest sind Sie damit bei der Kompilierung sicher, und ich vermute, dass es aus Performance-Gründen besser ist, als den Typ zur Laufzeit zu überprüfen.

0
Ehouarn Perret

Ich benutze

    public T Store<T>()
    {
        Type t = typeof(T);

        if (t == typeof(CategoryDataStore))
            return (T)DependencyService.Get<IDataStore<ItemCategory>>();
        else
            return default(T);
    }
0
mdimai666

Ja - verwenden Sie einfach den etwas seltsam benannten "Mustervergleich" von C # 7, um auf die Klasse oder Struktur zu passen:

IObject concrete1 = new ObjectImplementation1();
IObject concrete2 = new ObjectImplementation2();

switch (concrete1)
{
    case ObjectImplementation1 c1: return "type 1";         
    case ObjectImplementation2 c2: return "type 2";         
}
0
James Harcourt

Sollte funktionieren mit

fallart _:

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

Dies ist eine alternative Antwort, die Beiträge von JaredPar- und VirtLink-Antworten mit den folgenden Einschränkungen vermischt:

  • Die Switch-Konstruktion verhält sich als Funktion und empfängt Funktionen als Parameter an Fälle.
  • Stellt sicher, dass es ordnungsgemäß erstellt wurde und immer eine default-Funktion vorhanden ist.
  • Es kehrt nach dem ersten Treffer zurück (wahr für JaredPar-Antwort, nicht für VirtLink-Eins).

Verwendungszweck:

 var result = 
   TSwitch<string>
     .On(val)
     .Case((string x) => "is a string")
     .Case((long x) => "is a long")
     .Default(_ => "what is it?");

Code:

public class TSwitch<TResult>
{
    class CaseInfo<T>
    {
        public Type Target { get; set; }
        public Func<object, T> Func { get; set; }
    }

    private object _source;
    private List<CaseInfo<TResult>> _cases;

    public static TSwitch<TResult> On(object source)
    {
        return new TSwitch<TResult> { 
            _source = source,
            _cases = new List<CaseInfo<TResult>>()
        };
    }

    public TResult Default(Func<object, TResult> defaultFunc)
    {
        var srcType = _source.GetType();
       foreach (var entry in _cases)
            if (entry.Target.IsAssignableFrom(srcType))
                return entry.Func(_source);

        return defaultFunc(_source);
    }

    public TSwitch<TResult> Case<TSource>(Func<TSource, TResult> func)
    {
        _cases.Add(new CaseInfo<TResult>
        {
            Func = x => func((TSource)x),
            Target = typeof(TSource)
        });
        return this;
    }
}
0
jruizaranguren