Existe-t-il une meilleure alternative à «activer le type»?

Le fait de voir C # ne peut pas activer un type (ce qui, je suppose, n’a pas été ajouté comme un cas particulier car les relations est-un signifient que plusieurs cas distincts peuvent s’appliquer) ?

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 est définitivement absente en C # ( UPDATE: dans C # 7 / VS 2017, la commutation sur les types est prise en charge – voir la réponse de Zachary Yates ci-dessous ). Pour ce faire, sans une grande instruction if / else if / else, vous devrez travailler avec une structure différente. J’ai écrit un article de blog pendant un certain temps en 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 la diffusion redondante et donner 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 

    Le code de TypeSwitch est en fait assez petit et peut facilement être intégré à votre projet.

     static class TypeSwitch { public class CaseInfo { public bool IsDefault { get; set; } public Type Target { get; set; } public Action 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(Action action) { return new CaseInfo() { Action = x => action(), Target = typeof(T) }; } public static CaseInfo Case(Action 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 (Release 15. *), vous pouvez utiliser Types dans les instructions de 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(""); 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 versions antérieures, vous pouvez utiliser une instruction switch, mais vous devrez utiliser une chaîne magique contenant le nom du type … ce qui n’est pas particulièrement adapté aux refactorisations (merci @nukefusion)

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

    Une option consiste à avoir un dictionnaire de Type to Action (ou un autre délégué). Recherchez l’action en fonction du type, puis exécutez-la. Je l’ai utilisé pour les usines avant maintenant.

    Avec la réponse de JaredPar à l’arrière 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 { ssortingng Name { get; } } class B : A { ssortingng LongName { get; } } class C : A { ssortingng FullName { get; } } class X { public ssortingng ToSsortingng(IFormatProvider provider); } class Y { public ssortingng GetIdentifier(); } public ssortingng GetName(object value) { ssortingng 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.ToSsortingng(CultureInfo.CurrentCulture)) .Case((Y x) => name = x.GetIdentifier()) .Default((x) => name = x.ToSsortingng()); return name; } 

    Notez que l’ordre des méthodes Case() est important.


    Obtenez le code complet et commenté pour ma classe TypeSwitch . Ceci est une version abrégée de travail:

     public static class TypeSwitch { public static Switch On(TSource value) { return new Switch(value); } public sealed class Switch { private readonly TSource value; private bool handled = false; internal Switch(TSource value) { this.value = value; } public Switch Case(Action action) where TTarget : TSource { if (!this.handled && this.value is TTarget) { action((TTarget) this.value); this.handled = true; } return this; } public void Default(Action action) { if (!this.handled) action(this.value); } } } 

    Créer une super-classe (S) et en faire hériter 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 aussi changer sa signature en Foo (S o), ce qui la rend sûre, et vous n’avez pas besoin de lancer cette exception laide.

    Si vous utilisiez C # 4, vous pourriez utiliser la nouvelle fonctionnalité dynamic pour obtenir 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’utilisation:

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

    La raison pour laquelle cela fonctionne est que l’invocation d’une méthode dynamic C # 4 a ses surcharges résolues au moment de l’exécution plutôt que lors de la compilation. J’ai écrit un peu plus sur cette idée récemment . Encore une fois, je voudrais juste rappeler que cela est probablement pire que toutes les autres suggestions, je l’offre simplement comme une curiosité.

    Vous devriez vraiment surcharger votre méthode, ne pas essayer de faire la désambiguïsation vous-même. Jusqu’à présent, la plupart des réponses ne prennent pas en compte les futures sous-classes, ce qui peut entraîner de graves problèmes de maintenance par la suite.

    J’ai aimé l’ utilisation de la saisie implicite par Virtlink pour rendre le commutateur beaucoup plus lisible, mais je n’aimais pas qu’un départ anticipé ne soit pas possible et que nous effectuions des allocations. Montons un peu la perf.

     public static class TypeSwitch { public static void On(TV value, Action action1) where T1 : TV { if (value is T1) action1((T1)value); } public static void On(TV value, Action action1, Action 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 value, Action action1, Action action2, Action 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. } 

    Eh bien, ç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 = ssortingng.Join(", ", Enumerable.Range(1, icase).Select(i => ssortingng.Format("Action action{0}", i))); var wheres = ssortingng.Join(" ", Enumerable.Range(1, icase).Select(i => ssortingng.Format("where T{0} : TV", i))); #> < #=GenWarning#> public static void On>(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.ToSsortingng(CultureInfo.CurrentCulture), (Y x) => name = x.GetIdentifier(), (object x) => name = x.ToSsortingng()); 

    Lisible et rapide. Maintenant, comme tout le monde continue à le souligner dans leurs réponses, et compte tenu de la nature de cette question, l’ordre est important dans la correspondance de type. Donc:

    • Mettez les types de feuilles en premier, les types de base plus tard.
    • Pour les types de pairs, placez d’abord les correspondances les plus probables pour optimiser les performances.
    • Cela implique qu’il n’y a pas besoin d’un cas particulier par défaut. Au lieu de cela, utilisez simplement le type le plus bas dans le lambda et mettez-le en dernier.

    Pour les types intégrés, vous pouvez utiliser l’énumération TypeCode. Veuillez noter que GetType () est un peu lent, mais probablement pas pertinent dans la plupart des cas.

     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 et une interface ou une classe de base avec une propriété ou une méthode abstraite …

    Implémentation de classe abstraite de 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; } } } 

    Implémentation d’une classe abstraite de 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 la 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 m’en parler également: cela présente l’avantage que vous pouvez l’utiliser littéralement pour n’importe quel type d’object, pas seulement ceux que vous définissez. Il a l’inconvénient d’être un peu plus grand et plus lent.

    Définissez d’abord 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, _ssortingng, _Foo, _TcpClient, }; private static Dictionary typeDict; static TypeEnumerator() { typeDict = new Dictionary(); typeDict[typeof(int)] = TypeEnumeratorTypes._int; typeDict[typeof(ssortingng)] = TypeEnumeratorTypes._ssortingng; typeDict[typeof(Foo)] = TypeEnumeratorTypes._Foo; typeDict[typeof(System.Net.Sockets.TcpClient)] = TypeEnumeratorTypes._TcpClient; } ///  /// Throws NullReferenceException and TypeEnumeratorException /// NullReferenceException /// TypeEnumeratorException public static TypeEnumeratorTypes EnumerateType(object theObject) { try { return typeDict[theObject.GetType()]; } catch (KeyNotFoundException) { throw new TypeEnumeratorException(theObject.GetType()); } } } 

    Et puis vous pouvez l’utiliser comme ceci:

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

    Étant donné que l’inheritance facilite la reconnaissance d’un object en tant que plusieurs types, je pense qu’un changement pourrait entraîner une mauvaise ambiguïté. Par exemple:

    Cas 1

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

    Cas 2

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

    Parce que s est une chaîne et un object. Je pense que lorsque vous écrivez un switch(foo) vous vous attendez à ce que foo corresponde à une et une seule des déclarations de case . Avec un commutateur sur les types, l’ordre dans lequel vous écrivez vos instructions de cas pourrait éventuellement changer le résultat de l’instruction entière de commutateur. 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) !!

    Je voudrais soit

    • utiliser la méthode de surcharge (comme x0n ), ou
    • utiliser des sous-classes (tout comme Pablo ), ou
    • appliquer le modèle de visiteur .

    J’ai regardé quelques options ici, reflétant ce que F # peut faire. F # supporte beaucoup mieux la commutation basée sur les types (bien que je rest fidèle à C # ;-p). Vous voudrez peut-être voir ici et ici .

    Une autre façon serait de définir une interface IThing puis de l’implémenter dans les deux classes. Voici le snipet:

     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(Ssortingng[] args) { } private void Foo(IThing a) { a.Move(); } } 

    Oui grâce à C # 7 cela peut être réalisé, voici comment ça se passe (en utilisant le modèle d’expression ):

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

    Créez une interface IFooable, puis créez vos classes A et B pour implémenter une méthode commune, qui à son tour appelle la méthode correspondante souhaitée:

     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 est préférable d’utiliser “as” à la place, en vérifiant d’abord avec “is” et ensuite en castant, comme cela vous faites 2 casts (coûteux).

    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 le type de paramètre dynamic pour contourner la vérification de type statique:

     Foo((dynamic)something); 

    Vous recherchez des 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 par rapport au switch (et if et les exceptions as control flow ) est qu’il est sécurisé à la compilation – il n’ya pas de gestionnaire par défaut ou de

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

    Si vous ajoutez un troisième élément à o, vous obtenez une erreur de compilation car vous devez append un gestionnaire Func dans l’appel de commutateur.

    Vous pouvez également faire un .Match qui renvoie une valeur plutôt que d’exécuter une instruction:

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

    Je suis d’accord avec Jon sur le fait d’avoir un hachage d’actions pour classer. Si vous conservez votre modèle, vous pouvez envisager d’utiliser plutôt la construction “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("..."); 

    La différence est que lorsque vous utilisez le patter si (foo is Bar) {((Bar) foo) .Action (); } vous faites le casting de type deux fois. Maintenant, peut-être que le compilateur optimisera et ne le fera qu’une seule fois – mais je ne compte pas sur lui.

    Comme le suggère Pablo, l’approche par interface est presque toujours la bonne chose à faire pour y faire face. Pour vraiment utiliser le commutateur, une autre alternative consiste à avoir un enum personnalisé 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 en quelque sorte implémenté dans BCL. Un exemple est MemberInfo.MemberTypes , un 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 ...... } } 

    Ceci est une réponse alternative qui combine les consortingbutions des réponses JaredPar et VirtLink, avec les contraintes suivantes:

    • La construction du commutateur se comporte comme une fonction et reçoit des fonctions en tant que parameters pour les boîtiers.
    • S’assure qu’il est correctement construit et qu’il existe toujours une fonction par défaut .
    • Il retourne après la première correspondance (vrai pour la réponse JaredPar, pas vrai pour VirtLink 1).

    Usage:

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

    Code:

     public class TSwitch { class CaseInfo { public Type Target { get; set; } public Func Func { get; set; } } private object _source; private List> _cases; public static TSwitch On(object source) { return new TSwitch { _source = source, _cases = new List>() }; } public TResult Default(Func defaultFunc) { var srcType = _source.GetType(); foreach (var entry in _cases) if (entry.Target.IsAssignableFrom(srcType)) return entry.Func(_source); return defaultFunc(_source); } public TSwitch Case(Func func) { _cases.Add(new CaseInfo { Func = x => func((TSource)x), Target = typeof(TSource) }); return this; } }