web-dev-qa-db-fra.com

C # vs Java Enum (pour ceux qui débutent en C #)

Je suis en train de programmer dans Java depuis un moment et je viens de me lancer dans un projet entièrement écrit en C #. J'essaie de me familiariser avec C # et j'ai remarqué que des énums étaient utilisés à plusieurs endroits dans mon nouveau projet, mais à première vue, ceux de C # semblent plus simplistes que l'implémentation de Java 1.5+. Quelqu'un peut-il énumérer les différences entre les énumérations C # et Java et comment surmonter ces différences? (Je ne veux pas commencer une guerre de langage, mais je veux juste savoir comment faire certaines choses en C # que je faisais auparavant en Java). Par exemple, quelqu'un pourrait-il publier un équivalent en C # du célèbre exemple Planet enum de Sun?

public enum Planet {
  MERCURY (3.303e+23, 2.4397e6),
  VENUS   (4.869e+24, 6.0518e6),
  EARTH   (5.976e+24, 6.37814e6),
  MARS    (6.421e+23, 3.3972e6),
  JUPITER (1.9e+27,   7.1492e7),
  SATURN  (5.688e+26, 6.0268e7),
  URANUS  (8.686e+25, 2.5559e7),
  Neptune (1.024e+26, 2.4746e7),
  PLUTO   (1.27e+22,  1.137e6);

  private final double mass;   // in kilograms
  private final double radius; // in meters
  Planet(double mass, double radius) {
      this.mass = mass;
      this.radius = radius;
  }
  public double mass()   { return mass; }
  public double radius() { return radius; }

  // universal gravitational constant  (m3 kg-1 s-2)
  public static final double G = 6.67300E-11;

  public double surfaceGravity() {
      return G * mass / (radius * radius);
  }
  public double surfaceWeight(double otherMass) {
      return otherMass * surfaceGravity();
  }
}

// Example usage (slight modification of Sun's example):
public static void main(String[] args) {
    Planet pEarth = Planet.EARTH;
    double earthRadius = pEarth.radius(); // Just threw it in to show usage

    // Argument passed in is earth Weight.  Calculate weight on each planet:
    double earthWeight = Double.parseDouble(args[0]);
    double mass = earthWeight/pEarth.surfaceGravity();
    for (Planet p : Planet.values())
       System.out.printf("Your weight on %s is %f%n",
                         p, p.surfaceWeight(mass));
}

// Example output:
$ Java Planet 175
Your weight on MERCURY is 66.107583
Your weight on VENUS is 158.374842
[etc ...]
171
Ogre Psalm33

Les énumérations dans le CLR sont simplement des constantes nommées. Le type sous-jacent doit être intégral. Dans Java, une énumération s'apparente davantage à une instance nommée d'un type. Ce type peut être assez complexe et, comme le montre votre exemple, contenir plusieurs champs de types différents.

Pour porter l'exemple en C #, je voudrais simplement changer l'énumération en une classe immuable et exposer des instances statiques en lecture seule de cette classe:

using System;
using System.Collections.Generic;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Planet planetEarth = Planet.MERCURY;

            double earthRadius = pEarth.Radius; // Just threw it in to show usage
            double earthWeight = double.Parse("123");
            double earthMass   = earthWeight / pEarth.SurfaceGravity();

            foreach (Planet p in Planet.Values)
                Console.WriteLine($"Your weight on {p} is {p.SurfaceWeight(mass)}");

            Console.ReadKey();
        }
    }

    public class Planet
    {
        public static readonly Planet MERCURY = new Planet("Mercury", 3.303e+23, 2.4397e6);
        public static readonly Planet VENUS   = new Planet("Venus", 4.869e+24, 6.0518e6);
        public static readonly Planet EARTH   = new Planet("Earth", 5.976e+24, 6.37814e6);
        public static readonly Planet MARS    = new Planet("Mars", 6.421e+23, 3.3972e6);
        public static readonly Planet JUPITER = new Planet("Jupiter", 1.9e+27, 7.1492e7);
        public static readonly Planet SATURN  = new Planet("Saturn", 5.688e+26, 6.0268e7);
        public static readonly Planet URANUS  = new Planet("Uranus", 8.686e+25, 2.5559e7);
        public static readonly Planet Neptune = new Planet("Neptune", 1.024e+26, 2.4746e7);
        public static readonly Planet PLUTO   = new Planet("Pluto", 1.27e+22, 1.137e6);

        public static IEnumerable<Planet> Values
        {
            get
            {
                yield return MERCURY;
                yield return VENUS;
                yield return EARTH;
                yield return MARS;
                yield return JUPITER;
                yield return SATURN;
                yield return URANUS;
                yield return Neptune;
                yield return PLUTO;
            }
        }

        public string Name   { get; private set; }
        public double Mass   { get; private set; }
        public double Radius { get; private set; }

        Planet(string name, double mass, double radius) => 
            (Name, Mass, Radius) = (name, mass, radius);

        // Wniversal gravitational constant  (m3 kg-1 s-2)
        public const double G = 6.67300E-11;
        public double SurfaceGravity()            => G * mass / (radius * radius);
        public double SurfaceWeight(double other) => other * SurfaceGravity();
        public override string ToString()         => name;
    }
}
202
Kent Boogaart

En C #, vous pouvez définir méthodes d'extension sur des énumérations, ce qui compense certaines des fonctionnalités manquantes.

Vous pouvez définir Planet comme une énumération et avoir également des méthodes d'extension équivalentes à surfaceGravity() et surfaceWeight().

J'ai utilisé des attributs personnalisés comme suggéré par Mikhail , mais la même chose pourrait être obtenue à l'aide d'un dictionnaire.

using System;
using System.Reflection;

class PlanetAttr: Attribute
{
    internal PlanetAttr(double mass, double radius)
    {
        this.Mass = mass;
        this.Radius = radius;
    }
    public double Mass { get; private set; }
    public double Radius { get; private set; }
}

public static class Planets
{
    public static double GetSurfaceGravity(this Planet p)
    {
        PlanetAttr attr = GetAttr(p);
        return G * attr.Mass / (attr.Radius * attr.Radius);
    }

    public static double GetSurfaceWeight(this Planet p, double otherMass)
    {
        return otherMass * p.GetSurfaceGravity();
    }

    public const double G = 6.67300E-11;

    private static PlanetAttr GetAttr(Planet p)
    {
        return (PlanetAttr)Attribute.GetCustomAttribute(ForValue(p), typeof(PlanetAttr));
    }

    private static MemberInfo ForValue(Planet p)
    {
        return typeof(Planet).GetField(Enum.GetName(typeof(Planet), p));
    }

}

public enum Planet
{
    [PlanetAttr(3.303e+23, 2.4397e6)]  MERCURY,
    [PlanetAttr(4.869e+24, 6.0518e6)]  VENUS,
    [PlanetAttr(5.976e+24, 6.37814e6)] EARTH,
    [PlanetAttr(6.421e+23, 3.3972e6)]  MARS,
    [PlanetAttr(1.9e+27,   7.1492e7)]  JUPITER,
    [PlanetAttr(5.688e+26, 6.0268e7)]  SATURN,
    [PlanetAttr(8.686e+25, 2.5559e7)]  URANUS,
    [PlanetAttr(1.024e+26, 2.4746e7)]  Neptune,
    [PlanetAttr(1.27e+22,  1.137e6)]   PLUTO
}
209
finnw

En C #, les attributs peuvent être utilisés avec des énumérations. Le bon exemple de ce modèle de programmation avec une description détaillée est ici (Codeproject)

public enum Planet
{
   [PlanetAttr(3.303e+23, 2.4397e6)]
   Mercury,
   [PlanetAttr(4.869e+24, 6.0518e6)]
   Venus
} 

Edit: Jon Skeet a récemment posé cette question et y a répondu: Quel est l'équivalent de l'énumération de Java en C #? = Les classes internes privées en C # - pourquoi ne sont-elles pas utilisées plus souvent?

Edit 2: voir le réponse acceptée qui étend cette approche de manière très brillante!

34
Mikhail

Les énumérations Java sont en fait des classes complètes pouvant avoir un constructeur privé, des méthodes, etc., tandis que les énumérations C # sont simplement des entiers nommés. L'implémentation de Java IMO est de loin supérieure.

Cette page devrait vous aider beaucoup lors de l'apprentissage de c # venant d'un Java camp. (Le lien pointe vers les différences concernant les énumérations (faites défiler vers le haut/bas pour d'autres choses)

12
Richard Walton

Quelque chose comme ça, je pense:

public class Planets 
{
    public static readonly Planet MERCURY = new Planet(3.303e+23, 2.4397e6);
    public static readonly Planet VENUS = new Planet(4.869e+24, 6.0518e6);
    public static readonly Planet EARTH = new Planet(5.976e+24, 6.37814e6);
    public static readonly Planet MARS = new Planet(6.421e+23, 3.3972e6);
    public static readonly Planet JUPITER = new Planet(1.9e+27,   7.1492e7);
    public static readonly Planet SATURN = new Planet(5.688e+26, 6.0268e7);
    public static readonly Planet URANUS = new Planet(8.686e+25, 2.5559e7);
    public static readonly Planet Neptune = new Planet(1.024e+26, 2.4746e7);
    public static readonly Planet PLUTO = new Planet(1.27e+22,  1.137e6);
}

public class Planet
{
    public double Mass {get;private set;}
    public double Radius {get;private set;}

    Planet(double mass, double radius)
    {
        Mass = mass;
        Radius = radius;
    }

    // universal gravitational constant  (m3 kg-1 s-2)
    private static readonly double G = 6.67300E-11;

    public double SurfaceGravity()
    {
        return G * Mass / (Radius * Radius);
    }

    public double SurfaceWeight(double otherMass)
    {
        return otherMass * SurfaceGravity();
    }
}

Ou combinez les constantes dans la classe Planet comme ci-dessus

4
Chris S

Voici une autre idée intéressante concernant le comportement personnalisé disponible en Java. Je suis arrivé avec la classe de base Enumeration suivante:

public abstract class Enumeration<T>
    where T : Enumeration<T>
{   
    protected static int nextOrdinal = 0;

    protected static readonly Dictionary<int, Enumeration<T>> byOrdinal = new Dictionary<int, Enumeration<T>>();
    protected static readonly Dictionary<string, Enumeration<T>> byName = new Dictionary<string, Enumeration<T>>();

    protected readonly string name;
    protected readonly int ordinal;

    protected Enumeration(string name)
        : this (name, nextOrdinal)
    {
    }

    protected Enumeration(string name, int ordinal)
    {
        this.name = name;
        this.ordinal = ordinal;
        nextOrdinal = ordinal + 1;
        byOrdinal.Add(ordinal, this);
        byName.Add(name, this);
    }

    public override string ToString()
    {
        return name;
    }

    public string Name 
    {
        get { return name; }
    }

    public static explicit operator int(Enumeration<T> obj)
    {
        return obj.ordinal;
    }

    public int Ordinal
    {
        get { return ordinal; }
    }
}

Son paramètre de type est fondamentalement juste pour que le nombre ordinal fonctionne correctement dans différentes énumérations dérivées. L'exemple Operator de Jon Skeet tiré de sa réponse à une autre question (http://stackoverflow.com/questions/1376312/whats-the-equivalent-of-javas-enum-in-c) ci-dessus devient alors:

public class Operator : Enumeration<Operator>
{
    public static readonly Operator Plus = new Operator("Plus", (x, y) => x + y);
    public static readonly Operator Minus =  new Operator("Minus", (x, y) => x - y);
    public static readonly Operator Times =  new Operator("Times", (x, y) => x * y);
    public static readonly Operator Divide = new Operator("Divide", (x, y) => x / y);

    private readonly Func<int, int, int> op;

    // Prevent other top-level types from instantiating
    private Operator(string name, Func<int, int, int> op)
        :base (name)
    {
        this.op = op;
    }

    public int Execute(int left, int right)
    {
        return op(left, right);
    }
}

Cela donne quelques avantages.

  • Soutien ordinale
  • Conversion en string et int, ce qui rend les instructions de commutateur réalisables
  • GetType () donnera le même résultat pour chacune des valeurs d'un type Énumération dérivé.
  • Les méthodes statiques de System.Enum peut être ajouté à la classe d'énumération de base pour permettre les mêmes fonctionnalités.
3
Andrew Cooper

Je soupçonne que les énumérations en C # ne sont que des constantes internes au CLR, mais pas très familières. J'ai décompilé des classes dans Java et je peux vous dire que vous voulez que les énums soient une fois que vous avez converti.

Java fait quelque chose de sournois. Il traite la classe enum comme une classe normale avec, autant que je puisse en juger, utiliser beaucoup de macros pour référencer les valeurs enum. Si vous avez une instruction case dans une classe Java qui utilise des énumérations, elle remplace les références enum aux entiers. Si vous devez accéder à chaîne, il crée un tableau de chaînes indexées par un ordinal il utilise dans chaque classe, je pense économiser sur la boxe.

Si vous téléchargez ce décompilateur, vous verrez comment il crée sa classe et l’intègre. Plutôt fascinant pour être honnête. J'avais l'habitude de ne pas utiliser la classe enum parce que je pensais que c'était trop lourd pour un tableau de constantes. Je l’aime mieux que la façon limitée dont vous pouvez les utiliser en C #.

http://members.fortunecity.com/neshkov/dj.html - Java decompiler

2
Paul Bruner

Les énumérations Java permettent des conversions de typesafe faciles à partir du nom à l’aide de la méthode valueOf générée par le compilateur, c.-à-d.

// Java Enum has generics smarts and allows this
Planet p = Planet.valueOf("MERCURY");

L'équivalent d'une énumération brute en C # est plus détaillé:

// C# enum - bit of hoop jumping required
Planet p = (Planet)Enum.Parse(typeof(Planet), "MERCURY");

Cependant, si vous suivez la route choisie par Kent, vous pouvez facilement implémenter une méthode ValueOf dans votre classe enum.

2
serg10

A Java enum est un sucre syntaxique qui permet de présenter les énumérations de manière OO. Ce sont des classes abstraites étendant la classe Enum en Java, et chaque valeur enum est semblable à Une implémentation publique finale statique de la classe enum. Regardez les classes générées, et pour un enum "Foo" avec 10 valeurs, vous verrez les classes "Foo $ 1" à "Foo $ 10" générées.

Cependant, je ne connais pas C #, je ne peux que spéculer sur le fait qu'une énumération dans cette langue ressemble davantage à une énumération traditionnelle dans les langues de style C. D'après une rapide recherche Google, je constate qu'ils peuvent contenir plusieurs valeurs. Elles sont donc probablement implémentées de la même manière, mais avec beaucoup plus de restrictions que ce que le compilateur Java permet.

2
JeeBee

nous venons de créer une extension enum pour c # https://github.com/simonmau/enum_ext

C'est juste une implémentation pour le typeafeenum, mais cela fonctionne très bien, nous avons donc créé un paquet à partager - amusez-vous avec elle

public sealed class Weekday : TypeSafeNameEnum<Weekday, int>
{
    public static readonly Weekday Monday = new Weekday(1, "--Monday--");
    public static readonly Weekday Tuesday = new Weekday(2, "--Tuesday--");
    public static readonly Weekday Wednesday = new Weekday(3, "--Wednesday--");
    ....

    private Weekday(int id, string name) : base(id, name)
    {
    }
}
2
simonmau

L’énumération in Java est beaucoup plus complexe que C # enum et donc plus puissante. Puisqu’il s’agit d’un autre sucre syntaxique à la compilation, je me demande s’il valait vraiment la peine d’avoir inclus le langage vu son contenu limité. Il est parfois plus difficile de garder des choses en dehors de la langue que de céder à la pression d'inclure une fonctionnalité mineure.

0
dmihailescu

Vous pouvez également utiliser une classe utilitaire pour chaque type enum qui contient une instance avec des données avancées pour chaque valeur enum.

public enum Planet
{
    MERCURY,
    VENUS
}

public class PlanetUtil
{
    private static readonly IDictionary<Planet, PlanetUtil> PLANETS = new Dictionary<Planet, PlanetUtil();

    static PlanetUtil()
    {
        PlanetUtil.PLANETS.Add(Planet.MERCURY, new PlanetUtil(3.303e+23, 2.4397e6));
        PlanetUtil.PLANETS.Add(Planet.VENUS, new PlanetUtil(4.869e+24, 6.0518e6));
    }

    public static PlanetUtil GetUtil(Planet planet)
    {
        return PlanetUtil.PLANETS[planet];
    }

    private readonly double radius;
    private readonly double mass;

    public PlanetUtil(double radius, double mass)
    {
        this.radius = radius;
        this.mass = mass;
    }

    // getter
}
0
djmj
//Review the sample enum below for a template on how to implement a JavaEnum.
//There is also an EnumSet implementation below.

public abstract class JavaEnum : IComparable {
    public static IEnumerable<JavaEnum> Values {
        get {
            throw new NotImplementedException("Enumeration missing");
        }
    }

    public readonly string Name;

    public JavaEnum(string name) {
        this.Name = name;
    }

    public override string ToString() {
        return base.ToString() + "." + Name.ToUpper();
    }

    public int CompareTo(object obj) {
        if(obj is JavaEnum) {
            return string.Compare(this.Name, ((JavaEnum)obj).Name);
        } else {
            throw new ArgumentException();
        }
    }


    //Dictionary values are of type SortedSet<T>
    private static Dictionary<Type, object> enumDictionary;
    public static SortedSet<T> RetrieveEnumValues<T>() where T : JavaEnum {
        if(enumDictionary == null) {
            enumDictionary = new Dictionary<Type, object>();
        }
        object enums;
        if(!enumDictionary.TryGetValue(typeof(T), out enums)) {
            enums = new SortedSet<T>();
            FieldInfo[] myFieldInfo = typeof(T).GetFields(BindingFlags.Static | BindingFlags.DeclaredOnly | BindingFlags.Public);
            foreach(FieldInfo f in myFieldInfo) {
                if(f.FieldType == typeof(T)) {
                    ((SortedSet<T>)enums).Add((T)f.GetValue(null));
                }
            }
            enumDictionary.Add(typeof(T), enums);
        }
        return (SortedSet<T>)enums;
    }
}


//Sample JavaEnum
public class SampleEnum : JavaEnum {
    //Enum values
    public static readonly SampleEnum A = new SampleEnum("A", 1);
    public static readonly SampleEnum B = new SampleEnum("B", 2);
    public static readonly SampleEnum C = new SampleEnum("C", 3);

    //Variables or Properties common to all enums of this type
    public int int1;
    public static int int2 = 4;
    public static readonly int int3 = 9;

    //The Values property must be replaced with a call to JavaEnum.generateEnumValues<MyEnumType>() to generate an IEnumerable set.
    public static new IEnumerable<SampleEnum> Values {
        get {
            foreach(var e in JavaEnum.RetrieveEnumValues<SampleEnum>()) {
                yield return e;
            }
            //If this enum should compose several enums, add them here
            //foreach(var e in ChildSampleEnum.Values) {
            //    yield return e;
            //}
        }
    }

    public SampleEnum(string name, int int1)
        : base(name) {
        this.int1 = int1;
    }
}


public class EnumSet<T> : SortedSet<T> where T : JavaEnum {
    // Creates an enum set containing all of the elements in the specified element type.
    public static EnumSet<T> AllOf(IEnumerable<T> values) {
        EnumSet<T> returnSet = new EnumSet<T>();
        foreach(T item in values) {
            returnSet.Add(item);
        }
        return returnSet;
    }

    // Creates an enum set with the same element type as the specified enum set, initially containing all the elements of this type that are not contained in the specified set.
    public static EnumSet<T> ComplementOf(IEnumerable<T> values, EnumSet<T> set) {
        EnumSet<T> returnSet = new EnumSet<T>();
        foreach(T item in values) {
            if(!set.Contains(item)) {
                returnSet.Add(item);
            }
        }
        return returnSet;
    }

    // Creates an enum set initially containing all of the elements in the range defined by the two specified endpoints.
    public static EnumSet<T> Range(IEnumerable<T> values, T from, T to) {
        EnumSet<T> returnSet = new EnumSet<T>();
        if(from == to) {
            returnSet.Add(from);
            return returnSet;
        }
        bool isFrom = false;
        foreach(T item in values) {
            if(isFrom) {
                returnSet.Add(item);
                if(item == to) {
                    return returnSet;
                }
            } else if(item == from) {
                isFrom = true;
                returnSet.Add(item);
            }
        }
        throw new ArgumentException();
    }

    // Creates an enum set initially containing the specified element(s).
    public static EnumSet<T> Of(params T[] setItems) {
        EnumSet<T> returnSet = new EnumSet<T>();
        foreach(T item in setItems) {
            returnSet.Add(item);
        }
        return returnSet;
    }

    // Creates an empty enum set with the specified element type.
    public static EnumSet<T> NoneOf() {
        return new EnumSet<T>();
    }

    // Returns a copy of the set passed in.
    public static EnumSet<T> CopyOf(EnumSet<T> set) {
        EnumSet<T> returnSet = new EnumSet<T>();
        returnSet.Add(set);
        return returnSet;
    }

    // Adds a set to an existing set.
    public void Add(EnumSet<T> enumSet) {
        foreach(T item in enumSet) {
            this.Add(item);
        }
    }

    // Removes a set from an existing set.
    public void Remove(EnumSet<T> enumSet) {
        foreach(T item in enumSet) {
            this.Remove(item);
        }
    }
}
0
Jim