371

Seeing as C# can't switch on a Type (which I gather wasn't added as a special case because is relationships mean that more than one distinct case might apply), is there a better way to simulate switching on type other than this?

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

31 に答える 31

365

Visual Studio 2017 (リリース 15.*) に同梱されているC# 7では、ステートメントで型を使用できますcase(パターン マッチング)。

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 では、 nameof() 演算子で switch ステートメントを使用できます(@Joey Adams に感謝)。

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

C# 5 以前では、switch ステートメントを使用できましたが、型名を含むマジック文字列を使用する必要があります... これは特にリファクタリングしにくいものです (@nukefusion に感謝)

switch(o.GetType().Name) {
  case "AType":
    break;
}
于 2008-11-18T15:08:41.907 に答える
286

タイプの切り替えはC#では間違いなく欠けています(更新:C#7 / VS 2017ではタイプの切り替えがサポートされています-ZacharyYatesの回答を参照してください)。大きなif/else if / elseステートメントなしでこれを行うには、別の構造で作業する必要があります。TypeSwitch構造を構築する方法を詳しく説明したブログ投稿をしばらく前に書きました。

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

短いバージョン:TypeSwitchは、冗長なキャストを防ぎ、通常のswitch/caseステートメントと同様の構文を提供するように設計されています。たとえば、標準のWindowsフォームイベントで動作しているTypeSwitchは次のとおりです。

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のコードは実際にはかなり小さく、プロジェクトに簡単に組み込むことができます。

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
        };
    }
}
于 2008-11-18T15:44:53.563 に答える
103

1 つのオプションは、辞書 from Typeto Action(または他のデリゲート) を持つことです。種類からアクションを調べて実行してください。私は今まで工場にこれを使用していました。

于 2008-11-18T15:07:42.143 に答える
49

JaredPar の答え頭の片隅に置いて、より適切TypeSwitchな構文のために型推論を使用する彼のクラスのバリアントを作成しました。

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()メソッドの順序が重要であることに注意してください。


TypeSwitchmy classのコメント付きの完全なコードを入手してください。これは作業の短縮版です:

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);
        }
    }
}
于 2012-04-05T08:46:31.337 に答える
14

スーパークラス (S) を作成し、A と B がそれを継承するようにします。次に、すべてのサブクラスが実装する必要がある S の抽象メソッドを宣言します。

これを行うと、「foo」メソッドは署名を Foo(S o) に変更することもでき、タイプセーフになり、その醜い例外をスローする必要がなくなります。

于 2008-11-18T15:07:58.227 に答える
9

はい、達成できる C# 7 のおかげです。これがどのように行われるかです (式 patternを使用):

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());
}
于 2017-04-05T13:53:57.767 に答える
8

C#4を使用している場合は、新しい動的機能を利用して、興味深い代替手段を実現できます。これが良いと言っているわけではありません。実際、遅くなる可能性が非常に高いようですが、ある程度の優雅さはあります。

class Thing
{

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

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

}

そして使用法:

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

これが機能する理由は、C#4の動的メソッド呼び出しでは、コンパイル時ではなく実行時にオーバーロードが解決されるためです。私はごく最近、このアイデアについてもう少し書きました。繰り返しになりますが、これはおそらく他のすべての提案よりもパフォーマンスが悪いことを繰り返し述べたいと思います。単に好奇心として提供しています。

于 2008-11-19T15:22:38.897 に答える
7

組み込み型の場合は、TypeCode 列挙を使用できます。GetType() は少し遅いですが、ほとんどの状況では関係ないことに注意してください。

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

カスタム型の場合、独自の列挙型と、抽象プロパティまたはメソッドを持つインターフェイスまたは基本クラスのいずれかを作成できます...

プロパティの抽象クラス実装

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

メソッドの抽象クラス実装

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

プロパティのインターフェース実装

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

メソッドのインターフェース実装

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

私の同僚の 1 人もこれについて教えてくれました。これには、定義したオブジェクトだけでなく、文字通りあらゆるタイプのオブジェクトに使用できるという利点があります。少し大きくて遅いという欠点があります。

まず、次のように静的クラスを定義します。

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

そして、次のように使用できます。

switch (TypeEnumerator.EnumerateType(someObject))
{
    case TypeEnumerator.TypeEnumeratorTypes._int:
        break;
    case TypeEnumerator.TypeEnumeratorTypes._string:
        break;
}
于 2013-11-15T19:45:16.707 に答える
7

自分で曖昧さを解消しようとするのではなく、実際にメソッドをオーバーロードする必要があります。これまでの回答のほとんどは、将来のサブクラスを考慮していません。これは、後で本当にひどいメンテナンスの問題につながる可能性があります。

于 2008-11-18T15:16:34.697 に答える
6

スイッチを読みやすくするためにVirtlinkが暗黙の型付けを使用していることは気に入りましたが、アーリーアウトが不可能であり、割り当てを行っていることは気に入りませんでした。少しパフォーマンスを上げてみましょう。

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

うーん、指が痛くなる。T4 でやってみましょう:

<#@ 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 の例を少し調整します。

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

読みやすく、高速です。さて、誰もが回答で指摘し続けているように、この質問の性質を考えると、型の照合では順序が重要です。したがって:

  • リーフ型を最初に置き、ベース型を後にします。
  • ピア タイプの場合、パフォーマンスを最大化するために、一致する可能性が高いものを最初に配置します。
  • これは、特別なデフォルト ケースが必要ないことを意味します。代わりに、ラムダで最も基本的な型を使用し、最後に配置します。
于 2013-06-18T15:59:46.770 に答える
5

継承によってオブジェクトが複数の型として認識されやすくなるため、切り替えによってあいまいさが悪化する可能性があると思います。例えば:

ケース1

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

ケース 2

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

s は文字列ありオブジェクトであるためです。a を書くとき、foo がステートメントswitch(foo)の 1 つだけに一致することを期待していると思います。case型を切り替えると、case ステートメントを記述する順序によって、switch ステートメント全体の結果が変わる可能性があります。それは間違っていると思います。

列挙された型が互いに継承されていないことを確認する、「typeswitch」ステートメントの型に対するコンパイラ チェックを考えることができます。しかし、それは存在しません。

foo is Tと同じではありませんfoo.GetType() == typeof(T)!!

于 2011-05-06T11:56:37.893 に答える
4

私はどちらかだろう

  • メソッドのオーバーロードを使用する ( x0nと同様)、または
  • サブクラスを使用する ( Pabloと同様)、または
  • 訪問者パターンを適用します。
于 2008-11-18T15:12:30.197 に答える
4

もう 1 つの方法は、インターフェイス IThing を定義し、それを両方のクラスに実装することです。スニペットは次のとおりです。

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();
    }
}
于 2008-11-18T15:57:21.370 に答える
3

You can create overloaded methods:

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

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

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

And cast the argument to dynamic type in order to bypass static type checking:

Foo((dynamic)something);
于 2013-01-04T12:30:56.557 に答える
3

で動作する必要があります

case type _:

お気に入り:

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;
}
于 2019-04-04T12:56:32.937 に答える
2

interface を作成し、IFooable次にクラスを作成して共通メソッドを実装します。これにより、必要な対応するメソッドが呼び出されます。AB

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

as代わりに、最初にチェックしてからキャストする方が良いことに注意してくださいis.2つのキャストを行うため、より高価になります。

于 2008-11-18T15:26:02.543 に答える
2

そのような場合、通常、述語とアクションのリストになります。これらの行に沿ったもの:

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);
    }
}
于 2008-11-18T15:14:04.203 に答える
1

ここで提供されている F# 機能に対するいくつかの回答のオプションを比較した後、F# が型ベースの切り替えをより適切にサポートしていることを発見しました (ただし、私はまだ C# に固執しています)。ここここ
を参照してください。

于 2008-11-18T15:22:43.907 に答える
0

これは、JaredPar と VirtLink の回答を組み合わせた代替の回答であり、次の制約があります。

  • switch 構造は関数として動作し、ケースへのパラメーターとして関数を受け取ります。
  • 適切に構築され、デフォルトの関数が常に存在することを確認します。
  • 最初の一致の後に返されます(JaredPar の回答の場合は true、VirtLink の回答の場合は true ではありません)。

使用法:

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

コード:

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;
    }
}
于 2016-08-23T10:59:41.593 に答える
0

Pablo が示唆するように、ほとんどの場合、これを処理するにはインターフェイス アプローチが適切です。スイッチを実際に利用するには、別の方法として、クラスで型を示すカスタム列挙型を使用することもできます。

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

これはBCLにも実装されています。1 つの例はMemberInfo.MemberTypesで、別の例はGetTypeCode次のようなプリミティブ型です。

void Foo(object o)
{
    switch (Type.GetTypeCode(o.GetType())) // for IConvertible, just o.GetTypeCode()
    {
        case TypeCode.Int16:
        case TypeCode.Int32:
        //etc ......
    }
}
于 2013-01-03T03:44:53.763 に答える
0

私が使う

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

        if (t == typeof(CategoryDataStore))
            return (T)DependencyService.Get<IDataStore<ItemCategory>>();
        else
            return default(T);
    }
于 2018-11-28T23:04:51.680 に答える
0

クラス名へのアクションのハッシュを持つことについて、ジョンに同意します。パターンを維持する場合は、代わりに "as" 構造を使用することを検討してください。

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

違いは、パターン if (foo is Bar) { ((Bar)foo).Action(); を使用する場合です。型キャストを 2 回行っています。これで、コンパイラーが最適化してその作業を 1 回だけ実行する可能性がありますが、私はそれを期待しません。

于 2008-11-18T15:23:21.470 に答える