C#ができないことを見るswitch Type(私が収集したものは特別なケースとして追加されませんでした。なぜなら、is-a関係は、複数の異なる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());
}
}
タイプの切り替えはC#で間違いなく欠けています(更新:C#7/VS 2017ではタイプの切り替えがサポートされています- 以下のZachary Yatesの答えを参照)。大きなif/else if/elseステートメントを使用せずにこれを行うには、異なる構造で作業する必要があります。 TypeSwitch構造の構築方法の詳細については、しばらく前にブログ記事を書きました。
http://blogs.msdn.com/jaredpar/archive/2008/05/16/switching-on-types.aspx
短いバージョン: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
};
}
}
C#7の場合 、Visual Studio 2017(リリース15. *)に同梱されているため、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つのオプションは、Type
からAction
(またはその他のデリゲート)の辞書を作成することです。タイプに基づいてアクションを検索し、実行します。私はこれを以前に工場で使用しました。
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);
}
}
}
スーパークラス(S)を作成し、AおよびBから継承させます。次に、すべてのサブクラスが実装する必要がある抽象メソッドをSで宣言します。
これを行うと、「foo」メソッドはそのシグネチャをFoo(S o)に変更し、タイプセーフになり、そのandい例外をスローする必要がなくなります。
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の動的メソッド呼び出しでは、コンパイル時ではなく実行時にオーバーロードが解決されるためです。私はこのアイデアについてもう少し書きました かなり最近 。繰り返しますが、これはおそらく他のすべての提案よりもパフォーマンスが悪いことを繰り返したいと思います。単に好奇心として提供しています。
自分で曖昧さを解消しようとするのではなく、メソッドをオーバーロードする必要があります。これまでの答えのほとんどは、将来のサブクラスを考慮に入れていないため、後でひどいメンテナンスの問題が発生する可能性があります。
組み込み型の場合は、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; }
}
私の同僚の一人もこのことを教えてくれました。これには、定義したオブジェクトだけでなく、文字通りあらゆるタイプのオブジェクトに使用できるという利点があります。少し大きく、遅いという欠点があります。
まず、次のような静的クラスを定義します。
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;
}
私は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());
読みやすく高速。さて、誰もが答えを指摘し続け、この質問の性質を考えると、型のマッチングでは順序が重要です。したがって:
継承がオブジェクトを複数のタイプとして認識しやすくすることを考えると、切り替えは悪いあいまいさをもたらす可能性があると思います。例えば:
ケース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は文字列であるためandオブジェクトです。 switch(foo)
を記述するとき、fooはcase
ステートメントの1つだけと一致すると予想します。タイプを切り替えると、caseステートメントを記述する順序によって、switchステートメント全体の結果が変わる可能性があります。それは間違っていると思います。
「typeswitch」ステートメントのタイプのコンパイラーチェックを考えて、列挙されたタイプが互いに継承しないことをチェックできます。しかし、それは存在しません。
foo is T
はfoo.GetType() == typeof(T)
!!と同じではありません
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;
}
はい、達成できるC#7のおかげです。方法は次のとおりです( expression 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());
}
もう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();
}
}
オーバーロードメソッドを作成できます。
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);
このような場合、通常は述語とアクションのリストになります。これらの線に沿って何か:
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);
}
}
インターフェースIFooable
を作成してから、A
およびB
クラスを作成して共通メソッドを実装し、次に必要な対応するメソッドを呼び出します。
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
を使用してからキャストする方が良いことに注意してください。
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つの配列(キー用と値)およびキーのハッシュコードを収集して、それぞれのキーに値を設定します。
したがって、私が知る限り、次のように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.
スイッチにとって意味のある名前とメソッド名でインターフェイスを作成します。それぞれを呼び出してみましょう:void Do()
を実装するよう指示するIDoable
。
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();
// ...
}
少なくとも、コンパイル時には安全であり、実行時には型をチェックするよりもパフォーマンスの面で優れていると思います。
F#の言語機能であるDiscriminated Unions
を探していますが、私が作成したOneOfと呼ばれるライブラリを使用して同様の効果を達成できます。
https://github.com/mcintyre321/OneOf
switch
(およびif
およびexceptions as control flow
)に対する主な利点は、コンパイル時に安全であることです。デフォルトのハンドラーやフォールスルーはありません。
void Foo(OneOf<A, B> o)
{
o.Switch(
a => a.Hop(),
b => b.Skip()
);
}
3番目の項目をoに追加すると、スイッチ呼び出し内にハンドラFuncを追加する必要があるため、コンパイラエラーが発生します。
ステートメントを実行するのではなく、値を返す.Match
を実行することもできます。
double Area(OneOf<Square, Circle> o)
{
return o.Match(
square => square.Length * square.Length,
circle => Math.PI * circle.Radius * circle.Radius
);
}
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 ......
}
}
あなたが期待しているクラスを知っているが、まだオブジェクトを持っていないなら、あなたはこれをすることさえできます:
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";
}
}
クラス名に対するアクションのハッシュを持つことについて、ジョンに同意します。パターンを保持する場合は、代わりに「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回実行しています。コンパイラは最適化され、それを一度だけ実行するかもしれません-しかし、私はそれに頼りません。
私が使う
public T Store<T>()
{
Type t = typeof(T);
if (t == typeof(CategoryDataStore))
return (T)DependencyService.Get<IDataStore<ItemCategory>>();
else
return default(T);
}
はい-C#7から少し変わった名前の「パターンマッチング」を使用して、クラスまたは構造を照合します。
IObject concrete1 = new ObjectImplementation1();
IObject concrete2 = new ObjectImplementation2();
switch (concrete1)
{
case ObjectImplementation1 c1: return "type 1";
case ObjectImplementation2 c2: return "type 2";
}
で動作するはずです
ケースタイプ_:
好む:
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;
}
これは、JaredParおよび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;
}
}