'Açma tipine' bundan daha iyi bir alternatif var mı?


331

C # switchtürünü göremiyorum (topladığım özel bir durum olarak eklenmedi, çünkü isilişkiler birden fazla farklı casegeçerli olabileceği anlamına geliyor ), bunun dışında bir türe geçişin benzetiminin daha iyi bir yolu var mı?

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

18
Meraktan, neden sadece polimorfizm kullanmıyorsunuz?

18
@jeyoung mühürlü sınıflar ve geçici durumlar için buna değmez
xyz



2
@jeyoung: Polimorfizmin kullanılamadığı tipik bir durum, değiştirilen türlerin switchifadeyi içeren kodu bilmemesi gerektiğidir . Bir örnek: Montaj A , bir dizi veri nesnesi içerir (bu değişmez, bir şartname belgesinde veya benzeri bir şekilde tanımlanır). Kurullar B , C ve D , her referans bir ve çeşitli veri nesneleri için bir dönüşüm temin A (bazı özel biçimine, örneğin bir seri / seri kaldırma). B , C ve D' deki tüm sınıf hiyerarşisini yansıtmanız ve fabrikaları kullanmanız ya da ...
VEYA Haritacı

Yanıtlar:


276

C # 'da tiplerin açılması kesinlikle yoktur ( GÜNCELLEME: C # 7 / VS 2017'de tiplerin açılması desteklenir - aşağıdaki Zachary Yates'in cevabına bakın ). Bunu büyük bir if / else if / else ifadesi olmadan yapmak için farklı bir yapı ile çalışmanız gerekir. Bir süre önce bir TypeSwitch yapısının nasıl oluşturulacağını detaylandıran bir blog yazısı yazdım.

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

Kısa sürüm: TypeSwitch, gereksiz dökümleri önlemek ve normal bir switch / case deyimine benzer bir sözdizimi vermek üzere tasarlanmıştır. Örneğin, burada standart bir Windows form olayında TypeSwitch iş başında

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

TypeSwitch kodu aslında oldukça küçüktür ve projenize kolayca eklenebilir.

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

26
"type == entry.Target", uyumlu türleri (örn. alt sınıflar) dikkate almak için "entry.Target.IsAssignableFrom (type)" olarak da değiştirilebilir.
Mark Cidade

Kod, alt sınıfların desteklenmesi için "entry.Target.IsAssignableFrom (type)" kullanacak şekilde değiştirildi.
Matt Howells

3
Belki de dikkat çeken bir şey, (anladığım kadarıyla) diğer tüm durumların kontrol edildiğinden emin olmak için en son 'varsayılan' eylemi belirtmek gerektiğidir. Bunun standart bir anahtarda bir gereklilik olmadığına inanıyorum - hiç kimsenin alttan başka bir yere 'varsayılan' yerleştirmeye çalıştığını gördüm. Bunun için birkaç başarısız güvenli seçenek, dizinin varsayılanın son (bit israf) olduğundan emin olmak için sipariş vermesi veya varsayılandan sonra işlenecek bir değişkenin öntanımlı hale getirilmesi olabilir foreach(bu yalnızca bir eşleşme bulunamazsa gerçekleşir)
musefan

Gönderen boşsa ne olur? GetType bir istisna atacak
Jon

İki öneri: null kaynağını varsayılanı çağırarak veya bir istisna atarak işleyin ve CaseInfoyalnızca tür değerine karşı kontrol ederek boole'den kurtulun (eğer null ise varsayılansa).
Felix K.

291

Visual Studio 2017 (Sürüm 15 *) ile birlikte gelen C # 7 ile , caseifadelerde Türleri kullanabilirsiniz (kalıp eşleme):

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

C # 6 ile, nameof () operatörü ile bir switch deyimi kullanabilirsiniz (teşekkürler @Joey Adams):

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

C # 5 ve öncesi ile bir switch deyimi kullanabilirsiniz, ancak özellikle refactor dostu olmayan tür adını içeren sihirli bir dize kullanmanız gerekir (teşekkürler @nukefusion)

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

1
Bu vaka typeof (string) ile çalışır.Ad: ... veya Valuetype ile olması gerekir?
Tomer W

3
Şaşkınlık kırabilir
Konrad Morawski

6
@nukefusion: Bu, parlak yeni nameof()operatörü kullanmadığınız sürece .
Joey Adams

21
Nameof (NamespaceA.ClassC) == nameof (NamespaceB.ClassC) doğru olduğundan bu yanıtı sevmiyorum.
ischas

7
(c # 7), nesneye erişmeniz gerekmiyorsa alt çizgi de kullanabilirsiniz:case UnauthorizedException _:
Assaf

101

Seçeneklerden biri, (veya başka bir delege) ' Typeden sözlüğe sahip olmaktır Action. Eylemi türe göre arayın ve yürütün. Bunu daha önce fabrikalar için kullandım.


31
Küçük not: 1: 1 maçlar için iyi, ancak kalıtım ve / veya arayüzlerle ilgili bir acı olabilir - özellikle de siparişin bir sözlükle korunması garanti edilmediğinden. Ama yine de, adil bir şekilde bunu yapmamın yolu ;-p So +1
Marc Gravell

@Marc: Bu paradigmada kalıtım veya arayüzler nasıl kırılır? Anahtarın bir tür olduğunu ve eylemin bir yöntem olduğunu varsayarsak, miras veya arayüzler aslında söyleyebildiğim kadarıyla doğru şeyi (TM) zorlamalıdır. Birden fazla eylem ve sipariş eksikliğiyle sorunu kesinlikle anlıyorum.
Harper Shelby

2
Bu tekniği geçmişte çokça kullandım, genellikle IoC Container'a taşınmadan önce
Chris Canal

4
Bu teknik, kalıtım ve arabirimler için ayrılır, çünkü kontrol ettiğiniz nesne ile aradığınız temsilci arasında bire bir yazışma gerekir. Sözlükte bir nesnenin birden çok arayüzünden hangisini bulmaya çalışmalısınız?
Robert Rossney

5
Özellikle bu amaç için bir sözlük oluşturuyorsanız, anahtar türünün değerini döndürmek için dizinleyiciyi aşırı yükleyebilirsiniz veya eksikse, üst sınıf yoksa, eğer eksikse, o zaman bu üst sınıf vb.
Erik Forbes

49

İle JaredPar cevabı kafamın arkasında, yazdım onun bir varyantını TypeSwitchkullanımları daha güzel bir sözdizimi için çıkarım yazın bu sınıfın:

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

Case()Yöntemlerin sırasının önemli olduğunu unutmayın.


Benim için tam ve yorumladı kodu alın TypeSwitchsınıfa . Bu, çalışan kısaltılmış bir sürümdür:

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

İyi bir çözüm gibi görünüyor ve bu konuda başka ne söylemek zorunda kaldığınızı görmek istedim ama blog öldü.
Wes Grant

1
Lanet olsun, haklısın. Webhost'um bir saatten beri bazı sorunlar yaşıyor. Bunun üzerinde çalışıyorlar. Blogumdaki yazı aslında buradaki yanıtla aynı, ancak tam kaynak koduna bir bağlantıyla.
Daniel AA Pelsmaeker

1
Bunun parantezleri basit bir "işlevsel" anahtara nasıl düşürdüğünü sevin. İyi iş!
James White

2
Ayrıca ilk vaka için bir uzantısı yöntemi ekleyebilirsiniz: public static Switch<TSource> Case<TSource, TTarget>(this TSource value, Action<TTarget> action) where TTarget : TSource. Bunu söyleyelimvalue.Case((C x) ...
Joey Adams

1
@JoeyAdams: Son önerinizi ve bazı küçük iyileştirmeleri ekledim. Ancak sözdizimini aynı tutuyorum.
Daniel AA Pelsmaeker

14

Bir üst sınıf (S) oluşturun ve A ve B'yi miras alın. Daha sonra S'de her alt sınıfın uygulaması gereken soyut bir yöntem beyan edin.

Bunu yapmak "foo" yöntemi de imzasını Foo (S o) olarak değiştirebilir, bu da onu güvenli bir şekilde yapabilir ve bu çirkin istisnayı atmanıza gerek yoktur.


Gerçek bruno, ama soru bunu önermiyor. Bunu Pablo'ya cevabınıza ekleyebilirsiniz.
Dana the Sane

Bence soru A ve B onlar A = String olabilir yeterince genel; B = Liste <int> örneğin ...
bruno conde

13

C # 7 veya üzeri desen eşleştirme kullanabilirsiniz:

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

Bunun için teşekkür ederim! Alt sınıfları da algılamak için kullanılabilir: if (this.TemplatedParent.GetType (). IsSubclassOf (typeof (RadGridView)))) şu şekilde değiştirilebilir: subRadGridView.IsSubclassOf (switch. this.TemplatedParent.GetType ()) case var subRadGridView typeof (RadGridView)):
Flemming Bonde Kentved

Yanlış yapıyorsun. Bkz Serge stajyer tarafından cevap ve hakkında okumak Liskov ikamesi ilkesi
0xf

8

Belirsizliği kendiniz yapmaya çalışmamanız için yönteminizi gerçekten aşırı yüklemelisiniz. Şimdiye kadar cevapların çoğu gelecekteki alt sınıfları dikkate almıyor, bu da daha sonra gerçekten korkunç bakım sorunlarına yol açabilir.


3
Aşırı yük çözünürlüğü statik olarak belirlenir, böylece hiç çalışmaz.
Neutrino

@Neutrino: Soruda, türün derleme zamanında bilinmediğini belirten hiçbir şey yok. Ve eğer öyleyse, aşırı yük, OP'nin orijinal kod örneği göz önüne alındığında, diğer seçeneklerden daha mantıklıdır.
Peter Duniho

Türünü belirlemek için 'if' veya 'switch' ifadesini kullanmaya çalıştığı gerçeğinin, türün derleme zamanında bilinmediğinin oldukça açık bir göstergesi olduğunu düşünüyorum.
Nötrino

@Neutrino, Sergey Berezovskiy'nin işaret ettiği gibi, C #'da dinamik olarak çözülmesi gereken bir türü temsil eden (derleme zamanı yerine çalışma zamanında) bir dinamik anahtar kelime olduğunu hatırlıyorum.
Davide Cannizzo

8

C # 4 kullanıyorsanız, ilginç bir alternatif elde etmek için yeni dinamik işlevselliği kullanabilirsiniz. Bunun daha iyi olduğunu söylemiyorum, aslında daha yavaş olacağı muhtemel görünüyor, ancak bunun için belirli bir zarafeti var.

class Thing
{

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

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

}

Ve kullanımı:

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

Bunun nedeni, bir C # 4 dinamik yöntem çağırma işleminin aşırı yüklenmelerini derleme zamanı yerine çalışma zamanında çözülmüş olmasıdır. Son zamanlarda bu fikir hakkında biraz daha yazdım . Yine, bunun muhtemelen diğer tüm önerilerden daha kötü performans gösterdiğini tekrarlamak istiyorum, bunu sadece bir merak olarak sunuyorum.


1
Bugün de aynı fikrim vardı. Tür adını açmaktan yaklaşık 3 kat daha yavaştır. Tabii ki daha yavaş göreceli (60.000.000 çağrı için, sadece 4 saniye) ve kod çok daha okunabilir, buna değer.
Daryl


7

Yerleşik türler için TypeCode numaralandırmasını kullanabilirsiniz. Lütfen GetType () yönteminin yavaş olduğunu, ancak çoğu durumda alakalı olmadığını unutmayın.

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

Özel türler için, kendi numaralandırmanızı ve soyut özellik veya yöntemle bir arabirim veya temel sınıf oluşturabilirsiniz ...

Mülkiyetin soyut sınıf uygulaması

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

Yöntemin soyut sınıf uygulaması

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

Mülkiyetin arayüz uygulaması

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

Yöntemin arayüz uygulaması

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

İş arkadaşlarımdan biri bana bundan da bahsetti: Bunun avantajı, sadece tanımladığınız nesneler için değil, kelimenin tam anlamıyla her türlü nesne için kullanabilmenizdir. Biraz daha büyük ve daha yavaş olmanın dezavantajına sahiptir.

Önce böyle bir statik sınıf tanımlayın:

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

Ve sonra şöyle kullanabilirsiniz:

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

İlkel türler için TypeCode () - varyantını eklediğiniz için teşekkür ederiz, çünkü C # 7.0 - varyantı bile bunlarla çalışmaz (ikisi de nameof () belli değildir)
Ole Albers

6

Virtlink'in geçişi daha okunabilir hale getirmek için örtük yazma kullanımını sevdim , ancak erken çıkışın mümkün olmadığını ve ayırma yaptığımızı sevmedim. Mükemmelliği biraz açalım.

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

Bu parmaklarımı incitiyor. T4'te yapalım:

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

Virtlink örneğini biraz ayarlamak:

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

Okunabilir ve hızlı. Şimdi, herkes yanıtlarında işaret etmeye devam ettiği ve bu sorunun doğası göz önüne alındığında, tür eşleştirmesinde düzen önemlidir. Bu nedenle:

  • Önce yaprak türlerini, sonra taban türlerini koyun.
  • Akran türleri için, mükemmelliği en üst düzeye çıkarmak için önce daha olası eşleşmeleri koyun.
  • Bu, özel bir varsayılan duruma gerek olmadığı anlamına gelir. Bunun yerine, lambda'daki en temel türü kullanın ve en son koyun.

5

Kalıtım, bir nesnenin birden fazla tür olarak tanınmasını kolaylaştırdığı göz önüne alındığında, bir anahtarın kötü belirsizliğe yol açabileceğini düşünüyorum. Örneğin:

Dava 1

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

Durum 2

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

Çünkü s bir dize ve bir nesnedir. Sanırım bir yazdığınızda switch(foo)foo'nun caseifadelerden sadece biriyle eşleşmesini beklersiniz . Türleri açtığınızda, büyük / küçük harf ifadelerinizi yazma sırası büyük olasılıkla tüm anahtar ifadesinin sonucunu değiştirebilir. Bence bu yanlış olur.

Numaralandırılmış türlerin birbirinden miras kalmadığını kontrol ederek bir "typeswitch" deyimi türleri üzerinde bir derleyici denetimi düşünebilirsiniz. Yine de mevcut değil.

foo is Tile aynı değil foo.GetType() == typeof(T)!!



4

Başka bir yol IThing bir arayüz tanımlamak ve daha sonra her iki sınıfta snipet uygulamak olacaktır:

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

4

C # 7.0 spesifikasyonuna göre, aşağıdakilerden casebirinde yer alan bir yerel değişkeni bildirebilirsiniz 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;
}

Bu, böyle bir şeyi yapmanın en iyi yoludur, çünkü bir çevirmenin bitsel işlemlerden ve booleankoşullardan hemen sonra çalıştırabileceği en hızlı işlemler olan yığın oluşturma ve basma işlemlerini içerir .

Bunu bir ile karşılaştırmak Dictionary<K, V>, burada çok daha az bellek kullanımı var: sözlük tutmak RAM'de daha fazla alan ve CPU tarafından iki dizi oluşturmak için daha fazla hesaplama gerektirir (biri anahtarlar için diğeri değerler için) ve koymak için anahtarlar için karma kodlar toplamak değerlerini ilgili tuşlara

Yani, bildiğim kadarıyla , operatörle aşağıdaki gibi bir if- then- elsebloğu kullanmak istemediğiniz sürece daha hızlı bir yol olabileceğine inanmıyorum is:

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.

3

Aşırı yüklenmiş yöntemler oluşturabilirsiniz:

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

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

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

Ve dynamicstatik tür denetimini atlamak için argümanı yazmak için yazın:

Foo((dynamic)something);

3

Desen eşleşmesinin C # 8 geliştirmeleri, bunu böyle yapmayı mümkün kıldı. Bazı durumlarda işi ve daha özlü yapar.

        public Animal Animal { get; set; }
        ...
        var animalName = Animal switch
        {
            Cat cat => "Tom",
            Mouse mouse => "Jerry",
            _ => "unknown"
        };

2

Discriminated UnionsHangisinin F #'ın bir dil özelliği olduğunu arıyorsunuz , ancak OneOf adlı bir kitaplık kullanarak benzer bir efekt elde edebilirsiniz

https://github.com/mcintyre321/OneOf

En büyük avantajı switch(ve ifve exceptions as control flow) derleme zamanı güvenli olmasıdır - varsayılan bir işleyici veya düşme yoktur

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

O öğesine üçüncü bir öğe eklerseniz, anahtar çağrısının içine bir işleyici Func eklemeniz gerektiğinden derleyici hatası alırsınız.

Ayrıca .Match, bir deyim yürütmek yerine bir değer döndüren bir değer de yapabilirsiniz :

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

2

Bir arabirim oluşturun IFooable, ardından Ave Bsınıflarınızı ortak bir yöntem uygulamak için yapın;

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

Unutmayın, bunun asyerine önce kontrol isedip ardından döküm yapmak daha iyi olur , çünkü bu şekilde 2 oyuncu kazanırsınız, bu yüzden daha pahalıdır.


2

Ben böyle durumlarda genellikle bir tahminler ve eylemler listesi ile sonuçlanır. Bu çizgiler boyunca bir şey:

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

Burada F # özelliklerine sağlanan birkaç cevap seçeneklerini karşılaştırdıktan sonra, F #'ı tür tabanlı anahtarlama için daha iyi bir desteğe sahip olduğunu keşfettim (yine de C # 'a bağlı kalıyorum). Burada ve burada
görmek isteyebilirsiniz .


2
<F # fişini buraya takın>
Overlord Zurg

1

Anahtarınız için anlamlı olan herhangi bir ad ve yöntem adıyla bir arayüz oluşturacaktım, sırasıyla onları çağıralım: IDoableuygulamayı söyler void Do().

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

ve yöntemi aşağıdaki gibi değiştirin:

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

En azından derleme zamanında güvendesiniz ve performans açısından, çalışma zamanında türü kontrol etmekten daha iyi olduğundan şüpheleniyorum.


1

C # 8 ile yeni anahtarla daha da özlü hale getirebilirsiniz. Ve atma seçeneği _ kullanıldığında, bunlara ihtiyacınız olmadığında sayısız değişken oluşturmaktan kaçınabilirsiniz, şöyle:

        return document switch {
            Invoice _ => "Is Invoice",
            ShippingList _ => "Is Shipping List",
            _ => "Unknown"
        };

Invoice ve ShippingList sınıflardır ve belge bunlardan herhangi biri olabilen bir nesnedir.


0

Jon ile sınıf isminde bir karma işlem yapması konusunda hemfikirim. Deseninizi korursanız, bunun yerine "as" yapısını kullanmayı düşünebilirsiniz:

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

Aradaki fark, (foo Bar ise) {((Bar) foo) ise pıtırtı kullandığınızda .Action (); } tür dökümünü iki kez yapıyorsunuz. Şimdi belki derleyici optimize edecek ve sadece bir kez çalışacaktır - ama buna güvenmem.


1
Gerçekten birden fazla çıkış noktası (dönüş) sevmiyorum, ancak buna sadık kalmak istiyorsanız, başlangıçta "if (o == null) atmak" ekleyin, çünkü daha sonra kadronun başarısız olup olmadığını bilmeyeceksiniz veya nesne boştu.
Sunny Milenov

0

Pablo'nun önerdiği gibi, arayüz yaklaşımı neredeyse her zaman bununla başa çıkmak için yapılacak doğru şeydir. Switch'i gerçekten kullanmak için başka bir alternatif, sınıflarınızda türünüzü belirten özel bir numaralandırmaya sahip olmaktır.

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

Bu BCL'de de bir çeşit uygulanmaktadır. Bir örnek MemberInfo.MemberTypes , diğeri GetTypeCodeilkel tiplerdir , örneğin :

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

0

Bu, JaredPar ve VirtLink yanıtlarının katkılarını aşağıdaki kısıtlamalarla karıştıran alternatif bir cevaptır:

  • Anahtar konstrüksiyonu bir fonksiyon gibi davranır ve vakalara parametre olarak fonksiyonları alır .
  • Düzgün oluşturulduğundan ve her zaman varsayılan bir işlev bulunduğundan emin olur .
  • Bu ilk eşleşmeden sonra döner (VirtLink biri için de geçerlidir JaredPar cevap değil için de geçerlidir).

Kullanımı:

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

Kod:

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

Evet - sadece C # 7'den biraz garip bir şekilde adlandırılan "desen eşleşmesi" ni sınıf veya yapıda eşleştirmek için kullanın:

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

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

0

kullanırım

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

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

0

İle çalışmalı

vaka türü _:

sevmek:

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

0

Eğer beklediğiniz sınıfı biliyorsanız ama hala bir nesneniz yoksa bunu bile yapabilirsiniz:

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";
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.