これよりも「タイプをオンにする」より良い代替策はありますか?


331

switchタイプにはC#を使用できないと見なして(is関係が複数の異なるものcaseが適用される可能性があるため、特別なケースとして追加されていません)、これ以外のタイプの切り替えをシミュレートするより良い方法はありますか?

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
好奇心から、ポリモーフィズムを使ってみませんか?

18
@jeyoungはクラスを封印しましたが、その場しのぎの状況には価値がありません
xyz



2
@jeyoung:ポリモーフィズムを使用できない典型的な状況の1つは、切り替えられる型がswitchステートメントを含むコードを認識してはならない場合です。1つの例:アセンブリAには、一連のデータオブジェクトが含まれています(仕様オブジェクトなどで定義された、変更されないオブジェクト)。アセンブリBC、及びDの各基準Aからの様々なデータオブジェクトの変換を提供A(いくつかの特定の形式に例えばシリアライズ/デシリアライズします)。クラス階層全体をBC、およびDにミラーリングして、ファクトリーを使用するか、または...
またはMapper 2015

回答:


276

タイプの切り替えはC#には欠けています(更新:C#7 / VS 2017ではタイプの切り替えがサポートされています- 以下のZachary Yatesの回答を参照してください)。大きな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
        };
    }
}

26
「type == entry.Target」を「entry.Target.IsAssignableFrom(type)」に変更して、互換性のあるタイプ(サブクラスなど)を考慮することもできます。
Mark Cidade、

「entry.Target.IsAssignableFrom(type)」を使用するようにコードを変更し、サブクラスがサポートされるようにしました。
マットハウエルズ

3
おそらく注目に値することの1つは、(私が理解していることから)他のすべてのケースが確実にチェックされるように、「デフォルト」アクションを最後に指定する必要があることです。これは標準スイッチの要件ではないと私は信じています。私が誰かが底以外のどこかに「デフォルト」を植えようとするのを見たことはありません。このためのいくつかのフェイルセーフオプションは、配列がデフォルトであることを確認するために配列を順序付ける(ビットを浪費する)か、後に処理する変数にデフォルトをポップするforeach(一致が見つからなかった場合にのみ発生します)
musefan

送信者がnullの場合はどうなりますか?メソッドGetTypeは例外をスローします
ジョン

2つの提案:デフォルトを呼び出すか、例外をスローしてnullソースを処理CaseInfoし、型の値をチェックするだけでブール値を削除します(nullの場合はデフォルトです)。
Felix K.

291

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

1
これはケースtypeof(string).Name:...で機能しますか、それともValuetypeである必要がありますか?
Tomer W

3
難読化はそれを壊すことができます
Konrad Morawski

6
@nukefusion:つまり、光沢のある新しいnameof()演算子を使用しない限りです。
Joey Adams

21
nameof(NamespaceA.ClassC)== nameof(NamespaceB.ClassC)はtrueなので、この回答は好きではありません。
ischas 2015年

7
あなたがオブジェクトにアクセスする必要がない場合は(#7 C)あなたはまた、アンダースコアを使用することができますcase UnauthorizedException _:
アサフS.

101

1つのオプションは、辞書をTypeto Action(または他のデリゲート)にすることです。タイプに基づいてアクションを検索し、実行します。これは以前、工場で使用したことがあります。


31
細かい注意:1対1の一致には適していますが、継承やインターフェイスに問題がある可能性があります。特に、順序が辞書で保持されることが保証されていないためです。しかし、それでも、それは私がかなりの数の場所で行う方法です;-p So +1
Marc Gravell

@Marc:このパラダイムで継承やインターフェースはどのように機能しなくなるのでしょうか キーがタイプであり、アクションがメソッドであるとすると、継承またはインターフェイスは、私が知る限り、実際にはRight Thing(TM)を強制する必要があります。私は確かに、複数のアクションと順序の欠如の問題を理解しています。
Harper Shelby

2
私は過去にこのテクニックをたくさん使ってきました。通常はIoCコンテナに移行する前
Chris Canal

4
チェックするオブジェクトと呼び出すデリゲートの間に1対1の対応が必要なため、この手法は継承とインターフェイスに分解されます。オブジェクトの複数のインターフェースのうち、辞書で見つけようとするものはどれですか。
ロバートロスニー2008年

5
特にこの目的のためにディクショナリを構築している場合は、インデクサーをオーバーロードしてキータイプの値を返すか、またはスーパークラスが見つからない場合は、スーパークラスが見つからない場合は、スーパークラスなどがなくなるまで続けます。
エリックフォーブス

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


TypeSwitchクラスのコメント付きの完全なコードを取得します。これは動作する省略バージョンです:

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

良い解決策のように見え、あなたがそれについて他に何を言わなければならないかを見たかったが、ブログは死んでいる。
ウェスグラント

1
くそー、あなたは正しい。私のウェブホストは、1時間以来いくつかの問題を抱えています。彼らはそれに取り組んでいます。私のブログの投稿は基本的にここでの回答と同じですが、完全なソースコードへのリンクが含まれています。
Daniel AA Pelsmaeker

1
これがifブラケットを単純な「機能」スイッチにまとめる方法が大好きです。よくやった!
James White

2
最初のケースに拡張メソッドを追加することもできます:public static Switch<TSource> Case<TSource, TTarget>(this TSource value, Action<TTarget> action) where TTarget : TSource。これはあなたに言うことができますvalue.Case((C x) ...
ジョーイ・アダムス

1
@JoeyAdams:いくつかの小さな改善とともに、最後の提案を組み込みました。ただし、構文は同じにしています。
Daniel AA Pelsmaeker、2015年

14

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

これを「foo」メソッドで行うと、シグニチャーをFoo(S o)に変更してタイプセーフにすることができ、その醜い例外をスローする必要がありません。


本当のブルーノですが、質問はそれを示唆していません。あなたはパブロを通して答えにそれを含めることができます。
ダナセイン

質問から、AとBはA =文字列になることができるほど一般的だと思います。B = List <int>たとえば...
bruno conde '18年

13

C#7以降ではパターンマッチングを使用できます。

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

これをありがとう!サブクラスの検出にも使用できます:if(this.TemplatedParent.GetType()。IsSubclassOf(typeof(RadGridView)))を次のように変更できます:switch(this.TemplatedParent.GetType())case var subRadGridView when subRadGridView.IsSubclassOf( typeof(RadGridView)):
Flemming Bonde Kentved '25

あなたはそれを誤解している。Serge Intern回答を参照し、Liskov置換の原則
0xF

8

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


3
オーバーロードの解決は静的に決定されるため、まったく機能しません。
ニュートリノ2013年

@Neutrino:コンパイル時に型が不明であることを示す質問には何もありません。その場合、OPの元のコード例を考えると、オーバーロードは他のどのオプションよりも理にかなっています。
Peter Duniho

彼が「if」または「switch」ステートメントを使用して型を判別しようとしているという事実は、型がコンパイル時に不明であることをかなり明確に示していると思います。
ニュートリノ

@ Neutrino、Sergey Berezovskiyが指摘したように、C#には動的キーワードがあることを覚えています。これは、動的に解決する必要がある型を表します(実行時ではなく、コンパイル時)。
Davide

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


1
今日も同じ考えでした。タイプ名をオンにするよりも約3倍遅くなります。もちろん、遅いのは相対的であり(60,000,000コールの場合、わずか4秒です)、コードは非常に読みやすく、その価値があります。
ダリル


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

プリミティブ型のTypeCode()バリアントを追加していただきありがとうございます。これは、C#7.0でもバリアントが機能しないためです(nameof()は明らかに機能しません)
Ole Albers

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

読みやすく、高速です。さて、誰もが答えを指摘し続け、この質問の性質を考えると、型のマッチングでは順序が重要です。したがって:

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

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は文字列オブジェクトだからです。私は、あなたがを書くとき、switch(foo)fooがcaseステートメントの1つだけに一致することを期待していると思います。タイプを切り替えると、caseステートメントを記述する順序によって、switchステートメント全体の結果が変わる可能性があります。それは間違っていると思います。

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

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



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

4

C#7.0仕様に従って、あなたはでスコープのローカル変数を宣言することができますcaseのを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;
}

これはそのようなことを行うための最良の方法です。キャストとスタックへのプッシュ操作が含まれるため、インタプリタがビット単位の操作とboolean条件の直後に実行できる最速の操作です。

これをと比較するとDictionary<K, V>、メモリ使用量ははるかに少なくなります。ディクショナリを保持するには、RAMに多くのスペースが必要です。また、2つの配列(キー用と値用)を作成し、キーのハッシュコードを収集するためのCPUによる計算が必要です。それぞれのキーに対する値。

だから、これまでのように私が知っているために、私はあなただけを使用する場合を除き、より高速な方法が存在することができると信じていませんif- then- elseとブロック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

オーバーロードされたメソッドを作成できます。

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

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

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

dynamic静的型チェックをバイパスするために、引数をtypeにキャストします。

Foo((dynamic)something);

3

パターンマッチングのC#8の機能強化により、このようにすることが可能になりました。いくつかのケースではそれは仕事をし、より簡潔です。

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

2

あなたはDiscriminated UnionsF#の言語機能を探していますが、OneOfと呼ばれる私が作成したライブラリを使用することで同様の効果を得ることができます

https://github.com/mcintyre321/OneOf

switch(およびifandを超える)の主な利点exceptions as control flowは、コンパイル時に安全であることです。デフォルトのハンドラーがないか、フォールスルーしません。

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

3番目の項目をoに追加すると、スイッチ呼び出し内にハンドラーFuncを追加する必要があるため、コンパイラエラーが発生します。

.Matchステートメントを実行するのではなく、値を返すaを実行することもできます。

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

2

インターフェースを作成し、およびクラスを作成して共通のメソッドを実装し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つのキャストを行うため、コストが高くなります。


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


1

私はあなたのスイッチに意味のある名前とメソッド名でインターフェースを作成します、それぞれそれらを呼び出しましょう:IDoableそれは実装するように指示します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();
    }
}

メソッドを次のように変更します。

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

少なくともそれであなたはコンパイル時に安全であり、私は実行時にタイプをチェックするよりもパフォーマンスの面で優れていると思います。


1

C#8以降では、新しいスイッチでさらに簡潔にすることができます。また、破棄オプション_を使用すると、次のように、不要なときに必要な変数を作成する必要がなくなります。

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

InvoiceとShippingListはクラスであり、documentはそれらのいずれかになるオブジェクトです。


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

違いは、(fooがBar){((Bar)foo).Action();の場合にパターンを使用する場合です。}型キャストを2回実行しています。多分コンパイラは最適化し、それを一度だけ動作させます-しかし、私はそれを当てにしません。


1
私は実際には複数の出口ポイント(戻り)が好きではありませんが、これに固執したい場合は、最初に「if(o == null)throw」を追加します。後でキャストが失敗したかどうかはわかりません。オブジェクトはnullでした。
Sunny Milenov、2008年

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で、もう1つは次のGetTypeCodeようなプリミティブ型の例です。

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

0

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

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

使用法:

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

0

はい-少し奇妙な名前のC#7以降の「パターンマッチング」を使用して、クラスまたは構造を照合します。

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

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

0

私が使う

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

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

0

で動作するはずです

ケースタイプ_:

お気に入り:

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

あなたが期待しているクラスを知っていても、まだオブジェクトがない場合は、これを行うこともできます:

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";
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.