Voyant que C # ne peut pas basculer sur un type (qui, je suppose, n’a pas été ajouté à un cas particulier car les relations is-a signifient que plus d’un cas distinct cas pourrait s’appliquer), moyen de simuler le changement de type que cela?
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());
}
}
L'activation des types fait définitivement défaut en C # (UPDATE: en C # 7/VS 2017, l'activation des types est prise en charge - voir la réponse de Zachary Yates ci-dessous). Pour ce faire sans une instruction if/else if/else importante, vous devrez utiliser une structure différente. J'ai écrit un article de blog quelques temps en arrière détaillant comment construire une structure TypeSwitch.
http://blogs.msdn.com/jaredpar/archive/2008/05/16/switching-on-types.aspx
Version courte: TypeSwitch est conçu pour empêcher les transtypages redondants et donne une syntaxe similaire à une instruction switch/case normale. Par exemple, voici TypeSwitch en action sur un événement de formulaire Windows standard
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"));
Le code pour TypeSwitch est en fait assez petit et peut facilement être inséré dans votre projet.
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
};
}
}
Avec C # 7 , fourni avec Visual Studio 2017 (version 15. *), vous pouvez utiliser les types dans les instructions case
(correspondance de modèle):
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));
}
Avec C # 6, vous pouvez utiliser une instruction switch avec l’opérateur nameof () (merci @Joey Adams):
switch(o.GetType().Name) {
case nameof(AType):
break;
case nameof(BType):
break;
}
Avec C # 5 et les versions antérieures, vous pouvez utiliser une instruction switch, mais vous devrez utiliser une chaîne magique contenant le nom du type ... qui n'est pas particulièrement adaptée aux refactors (merci @nukefusion)
switch(o.GetType().Name) {
case "AType":
break;
}
Une option est d'avoir un dictionnaire de Type
à Action
(ou un autre délégué). Recherchez l'action en fonction du type, puis exécutez-la. J'ai utilisé cela pour les usines avant maintenant.
Avec la réponse de JaredPar au fond de ma tête, j'ai écrit une variante de sa classe TypeSwitch
qui utilise l'inférence de type pour une syntaxe plus agréable:
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;
}
Notez que l'ordre des méthodes Case()
est important.
Récupère le code complet et commenté de ma classe TypeSwitch
. Ceci est une version abrégée de travail:
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);
}
}
}
Créez une super-classe (S) et faites-en hériter par A et B. Puis déclarez une méthode abstraite sur S que chaque sous-classe doit implémenter.
En faisant cela, la méthode "foo" peut également changer sa signature en Foo (S o), ce qui le rend sûr, et vous n'avez pas besoin de lancer cette mauvaise exception.
Si vous utilisiez C # 4, vous pourriez utiliser la nouvelle fonctionnalité dynamique pour trouver une alternative intéressante. Je ne dis pas que c'est mieux, en fait, il semble très probable que ce serait plus lent, mais cela a une certaine élégance.
class Thing
{
void Foo(A a)
{
a.Hop();
}
void Foo(B b)
{
b.Skip();
}
}
Et l'usage:
object aOrB = Get_AOrB();
Thing t = GetThing();
((dynamic)t).Foo(aorB);
Cela s'explique par le fait que les surcharges d'un appel de méthode dynamique C # 4 sont résolues au moment de l'exécution plutôt qu'à la compilation. J'ai écrit un peu plus sur cette idée tout récemment . Encore une fois, je voudrais juste répéter que cela fonctionne probablement moins bien que toutes les autres suggestions, je l'offre simplement par curiosité.
Vous devriez vraiment surcharger votre méthode et ne pas essayer de faire la désambiguïsation vous-même. Jusqu'à présent, la plupart des réponses ne tiennent pas compte des futures sous-classes, ce qui pourrait entraîner des problèmes de maintenance vraiment terribles plus tard.
Pour les types intégrés, vous pouvez utiliser l'énumération TypeCode. Notez que GetType () est un peu lent, mais probablement pas pertinent dans la plupart des situations.
switch (Type.GetTypeCode(someObject.GetType()))
{
case TypeCode.Boolean:
break;
case TypeCode.Byte:
break;
case TypeCode.Char:
break;
}
Pour les types personnalisés, vous pouvez créer votre propre énumération, ainsi qu'une interface ou une classe de base avec une propriété abstraite ou une méthode ...
Implémentation de classe abstraite de la propriété
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; } }
}
Mise en oeuvre de la classe abstraite de la méthode
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; }
}
Implémentation d'interface de propriété
public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public interface IFooType
{
FooTypes FooType { get; }
}
public class FooFighter : IFooType
{
public FooTypes FooType { get { return FooTypes.FooFighter; } }
}
Implémentation d'interface de méthode
public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public interface IFooType
{
FooTypes GetFooType();
}
public class FooFighter : IFooType
{
public FooTypes GetFooType() { return FooTypes.FooFighter; }
}
Un de mes collègues vient de me le dire aussi: Cela présente l’avantage de pouvoir l’utiliser pour littéralement tout type d’objet, pas seulement ceux que vous définissez. Il a le désavantage d'être un peu plus grand et plus lent.
Commencez par définir une classe statique comme ceci:
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());
}
}
}
Et ensuite, vous pouvez l'utiliser comme ceci:
switch (TypeEnumerator.EnumerateType(someObject))
{
case TypeEnumerator.TypeEnumeratorTypes._int:
break;
case TypeEnumerator.TypeEnumeratorTypes._string:
break;
}
J'aimais utiliser l'utilisation du typage implicite par Virtlink pour rendre le commutateur beaucoup plus lisible, mais je n'aimais pas le fait qu'un départ précoce ne soit pas possible et que nous faisons des allocations. Allons un peu en l'air.
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.
}
Ça me fait mal aux doigts. Faisons-le en 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#>
}
Ajuster un peu l'exemple de 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());
Lisible et rapide Maintenant, comme tout le monde continue de le souligner dans ses réponses, et étant donné la nature de cette question, l’ordre est important dans la correspondance des types. Donc:
Étant donné que l'héritage facilite la reconnaissance d'un objet comme appartenant à plus d'un type, je pense qu'un basculement pourrait conduire à une mauvaise ambiguïté. Par exemple:
Cas 1
{
string s = "a";
if (s is string) Print("Foo");
else if (s is object) Print("Bar");
}
Cas 2
{
string s = "a";
if (s is object) Print("Foo");
else if (s is string) Print("Bar");
}
Parce que s est une chaîne et un objet ..__ Je pense que lorsque vous écrivez une switch(foo)
, vous vous attendez à ce que foo corresponde à une et une seule des déclarations case
. Avec un type d'activation, l'ordre dans lequel vous écrivez vos instructions case peut éventuellement changer le résultat de l'instruction switch. Je pense que ce serait faux.
Vous pourriez penser à une vérification du compilateur sur les types d'une instruction "typeswitch", en vérifiant que les types énumérés n'héritent pas les uns des autres. Cela n'existe pas cependant.
foo is T
n'est pas la même chose que foo.GetType() == typeof(T)
!!
Oui, grâce à C # 7, cela peut être réalisé, voici comment procéder (en utilisant 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());
}
Une autre façon de faire serait de définir une interface ITching puis de l’implémenter dans les deux classes.
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();
}
}
Dans de tels cas, je termine généralement avec une liste de prédicats et d’actions. Quelque chose dans ce sens:
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);
}
}
Utilisez C # 7 et le filtrage par motif.
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;
}
Créez une interface IFooable, puis faites en sorte que vos classes A et B implémentent une méthode commune, qui appelle à son tour la méthode correspondante:
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());
}
}
}
Notez qu'il vaut mieux utiliser "en tant que" plutôt que de vérifier d'abord avec "est" puis de lancer, car cela vous permet de faire 2 conversions (coûteuses).
Vous pouvez créer des méthodes surchargées:
void Foo(A a)
{
a.Hop();
}
void Foo(B b)
{
b.Skip();
}
void Foo(object o)
{
throw new ArgumentException("Unexpected type: " + o.GetType());
}
Et utilisez dynamic type de paramètre pour ignorer la vérification de type statique:
Foo((dynamic)something);
Selon la spécification C # 7.0, vous pouvez déclarer une variable locale étendue dans une case
sur une switch
:
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;
}
Par hasard, demandez-vous pourquoi la variable est déclarée en tant que string _
? Pourquoi le soulignement?
Une autre fonctionnalité introduite avec C # 7.0 est que vous pouvez nommer en tant que telle une variable à laquelle vous ne faites jamais référence. Donc, vous ne pouvez pas référencer la variable _
. Il est bon dans de nombreux scénarios tels que ceux demandés par le PO, car il souhaite simplement vérifier le type et ne pas obtenir une référence moulée. Sinon, vous pouvez renommer cette variable et l'utiliser comme référence.
C’est la meilleure façon de procéder, car elle implique uniquement des opérations de transtypage et Push-on-the-stack, qui sont les opérations les plus rapides pouvant être exécutées par un interpréteur juste après des opérations au niveau du bit et des conditions boolean
.
Comparé à un Dictionary<K, V>
, l'utilisation de la mémoire est bien moindre: la tenue d'un dictionnaire nécessite plus d'espace dans la mémoire RAM et un peu plus de calcul par la CPU pour créer deux tableaux (l'un pour les clés et l'autre pour les valeurs) et la collecte de codes de hachage pour les clés pour mettre des valeurs à leurs clés respectives.
Donc, pour autant que je sache, je ne pense pas qu'un moyen plus rapide puisse exister même si vous ne souhaitez pas utiliser uniquement un bloc if
-then
-else
avec l'opérateur is
comme suit:
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.
Vous recherchez Discriminated Unions
qui sont une fonctionnalité de langage de F #, mais vous pouvez obtenir un effet similaire en utilisant une bibliothèque que j'ai créée, appelée OneOf.
https://github.com/mcintyre321/OneOf
Le principal avantage de switch
(et if
et exceptions as control flow
) est qu’il est protégé à la compilation - il n’existe aucun gestionnaire par défaut ni aucun
void Foo(OneOf<A, B> o)
{
o.Switch(
a => a.Hop(),
b => b.Skip()
);
}
Si vous ajoutez un troisième élément à o, vous obtiendrez une erreur du compilateur car vous devrez ajouter un gestionnaire Func à l'intérieur de l'appel du commutateur.
Vous pouvez également créer un .Match
qui renvoie une valeur plutôt que d'exécuter une instruction:
double Area(OneOf<Square, Circle> o)
{
return o.Match(
square => square.Length * square.Length,
circle => Math.PI * circle.Radius * circle.Radius
);
}
Comme le suggère Pablo, l'approche par interface est presque toujours la bonne chose à faire pour gérer cela. Pour utiliser vraiment switch, une autre alternative consiste à avoir une énumération personnalisée indiquant votre type dans vos classes.
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:
//......
}
}
Ceci est également implémenté dans BCL. Un exemple est MemberInfo.MemberTypes , l'autre est GetTypeCode
pour les types primitifs, comme
void Foo(object o)
{
switch (Type.GetTypeCode(o.GetType())) // for IConvertible, just o.GetTypeCode()
{
case TypeCode.Int16:
case TypeCode.Int32:
//etc ......
}
}
Si vous connaissez la classe que vous attendez mais que vous n'avez toujours pas d'objet, vous pouvez même le faire:
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";
}
}
Je suis d'accord avec Jon sur le fait d'avoir un hash d'actions au nom de la classe. Si vous conservez votre modèle, vous pouvez envisager d'utiliser la construction "en tant que" à la place:
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("...");
La différence est que lorsque vous utilisez le modèle si (toto est Bar) {((Bar) toto) .Action (); } tu fais le casting de type deux fois. Maintenant, peut-être que le compilateur optimisera et ne fera ce travail qu’une seule fois, mais je ne compterais pas dessus.
Je créerais une interface avec le nom de la méthode et le nom qui conviendraient le mieux pour votre commutateur. Appelons-les respectivement: IDoable
qui indique de mettre en œuvre 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();
}
}
et changez la méthode comme suit:
void Foo<T>(T obj)
where T : IDoable
{
// ...
obj.Do();
// ...
}
Au moins, avec cela, vous êtes en sécurité au moment de la compilation et je soupçonne que, du point de vue des performances, il est préférable de vérifier le type au moment de l'exécution.
J'utilise
public T Store<T>()
{
Type t = typeof(T);
if (t == typeof(CategoryDataStore))
return (T)DependencyService.Get<IDataStore<ItemCategory>>();
else
return default(T);
}
Oui, utilisez simplement le "filtrage de motif" légèrement bizarre nommé à partir de C # 7 pour apparier la classe ou la structure:
IObject concrete1 = new ObjectImplementation1();
IObject concrete2 = new ObjectImplementation2();
switch (concrete1)
{
case ObjectImplementation1 c1: return "type 1";
case ObjectImplementation2 c2: return "type 2";
}
Devrait travailler avec
type de cas _:
comme:
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;
}
Il s'agit d'une réponse alternative qui mélange les contributions des réponses JaredPar et VirtLink, avec les contraintes suivantes:
Usage:
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;
}
}