C # против Java Enum (для новичков в C #)


182

Я программировал на Java некоторое время и только что был брошен на проект, полностью написанный на C #. Я пытаюсь ускорить работу в C # и заметил, что перечисления используются в нескольких местах в моем новом проекте, но на первый взгляд перечисления C # кажутся более простыми, чем реализация Java 1.5+. Может кто-нибудь перечислить различия между C # и Java перечислениями, и как преодолеть различия? (Я не хочу начинать языковую войну пламени, я просто хочу знать, как делать некоторые вещи в C #, которые я делал в Java). Например, может ли кто-нибудь опубликовать аналог C # для известного примера перечисления Planet от 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 ...]

1
@ycomp Я не могу взять кредит на это. Это исходит от Sun (сейчас Oracle): docs.oracle.com/javase/tutorial/java/javaOO/enum.html
Ogre Psalm33

Ответы:


210

Перечисления в CLR просто называются константами. Базовый тип должен быть целочисленным. В Java перечисление больше похоже на именованный экземпляр типа. Этот тип может быть довольно сложным и - как показывает ваш пример - содержать несколько полей разных типов.

Чтобы перенести пример на C #, я бы просто изменил перечисление на неизменяемый класс и выставил статические только для чтения экземпляры этого класса:

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

4
Именно этот тип безопасных для типов перечислений, которые нам, беднякам, вынужденным использовать Java 1.4 и ниже, приходится реализовывать ... Перечисления Java 5, пожалуй, лучшая особенность Java 5+, тем более что они могут использоваться в выражениях switch.
MetroidFan2002

9
@Chris: только перечисления флагов должны быть множественными. То есть перечисления, члены которых объединяются с помощью | оператор.
Кент Boogaart

5
@Mladen: Это полностью зависит от контекста. Перечень планет может идеально подходить для игры, которая обеспечивает доступ к ограниченному количеству планет. Изменения в коде могут быть именно тем, что вы хотите, если в игру будет добавлена ​​новая планета.
Кент Boogaart

3
@Richie_W вы можете перебирать перечисления, используя свойство Values.
Джонатан

23
Ух ты ... Почти невероятно видеть что-то реализованное более многословно в C #, чем в Java
Sune Rasmussen

218

В C # вы можете определять методы расширения для перечислений, и это восполняет некоторые из отсутствующих функциональных возможностей.

Вы можете определить Planetкак enum, а также иметь методы расширения, эквивалентные surfaceGravity()и surfaceWeight().

Я использовал пользовательские атрибуты, предложенные Михаилом , но того же можно достичь с помощью словаря.

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
}

20
Я думаю, что за это нужно больше голосовать. Это ближе к тому, как работает перечисление в Java. Я могу сделать что-то вроде Planet.MERCURY.GetSurfaceGravity () <- Обратите внимание на метод расширения в Enum!
thenonhacker

2
Определенно да. Методы расширения в Enums (черт возьми, методы расширения в целом) являются отличным дополнением к C #.
KeithS

3
@AllonGuralnek Спасибо. Не все согласны с метаданными. См. Комментарий MattDavey по связанному вопросу codereview.stackexchange .
finnw

@finnw: я никогда не слышал о сайте Code Review SE - спасибо за это! Я продолжаю это обсуждение там (хотя это может заслуживать своего собственного вопроса здесь на Программистов).
Аллон Гуралнек

это отличное решение - кто-нибудь проверял производительность этого? например, сколько времени занимает доступ к атрибуту по сравнению с доступом к свойству класса.
Саймон Мейер

35

В C # атрибуты могут использоваться с перечислениями. Хороший пример этого шаблона программирования с подробным описанием здесь (Codeproject)

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

Изменить: этот вопрос был недавно задан снова и ответил Джон Скит: Что эквивалентно перечислению Java в C #? Частные внутренние классы в C # - почему они не используются чаще?

Изменить 2: увидеть принятый ответ, который расширяет этот подход очень блестящим образом!


1
Ницца! Это кажется немного неуклюжим, но в остальном это очень приемлемый метод для добавления дополнительных данных в перечисление. Я искренне удивлен, что кому-то понадобилось так много времени, чтобы упомянуть это отличное решение!
Огрский псалом33

13

Перечисления Java на самом деле являются полными классами, которые могут иметь закрытый конструктор, методы и т. Д., Тогда как перечисления C # просто называются целыми числами. Реализация IMO Java намного лучше.

Эта страница должна помочь вам во время изучения c # из java лагеря. (Ссылка указывает на различия в перечислениях (прокрутка вверх / вниз для других целей)


1
Хотя ваша ссылка дает интересный, обширный обзор сходств и различий между C # и Java, в тексте много ошибок (например, ошибочно говорится, что защищенная Java равна C # внутренней, тогда как она должна быть внутренней защищенной). Так что не принимайте все там как должное :)
Мафу

1
Я бы не сказал, что перечисления Java лучше, даже если я фанат Java. C # поддерживает простое целочисленное объявление, например, FOO = 0которое легче использовать в инструментах ORM (не ordinal()требуется использование, склонное к ошибкам ). Далее C # поддерживает побитовые перечисления, которые часто очень полезны, особенно в сочетании с EntityFramework. Java должна расширять свои перечисления, чтобы они тоже могли быть связаны с целыми числами. Тогда они будут лучше :)
djmj

4

Как то так я думаю

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

Или объедините константы в Planetкласс, как указано выше


8
Не совсем - конструктор Планеты должен быть закрытым; Частью перечислений является то, что они являются фиксированным набором значений. Значения также будут определены в классе Planet.
Джон Скит

Еще нет. 1) перечислитель отсутствует :) 2) перечисления никогда не должны быть изменяемыми. И, наконец, ваш код требует одного класса (особенно если у вас есть личный конструктор)
nawfal

3

Вот еще одна интересная идея, которая обслуживает пользовательское поведение, доступное в Java. Я придумал следующий Enumerationбазовый класс:

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

Он имеет параметр типа в основном просто для того, чтобы порядковый номер работал правильно для разных производных перечислений. OperatorПример Джона Скита из его ответа на другой вопрос (http://stackoverflow.com/questions/1376312/whats-the-equivalent-of-javas-enum-in-c) выше становится:

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

Это дает несколько преимуществ.

  • Порядковая поддержка
  • Преобразование stringи intчто делает операторы переключения осуществимыми
  • GetType () даст одинаковый результат для каждого из значений производного типа Enumeration.
  • Методы Static from System.Enumмогут быть добавлены в базовый класс Enumeration для обеспечения той же функциональности.

3

мы только что сделали расширение enum для c # https://github.com/simonmau/enum_ext

Это всего лишь реализация для typesafeenum, но она прекрасно работает, поэтому мы создали пакет, которым можно поделиться - развлекайтесь с ним

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

Перечисление Java является синтаксическим сахаром для представления перечислений в виде ОО. Это абстрактные классы, расширяющие класс Enum в Java, и каждое значение enum похоже на статическую конечную реализацию открытого экземпляра класса enum. Посмотрите на сгенерированные классы, и для перечисления «Foo» с 10 значениями вы увидите сгенерированные классы «Foo $ 1» - «Foo $ 10».

Я не знаю C #, но могу только предположить, что перечисление в этом языке больше похоже на традиционное перечисление в языках стиля C. Однако из быстрого поиска в Google видно, что они могут содержать несколько значений, поэтому они, вероятно, реализованы аналогичным образом, но с гораздо большими ограничениями, чем позволяет компилятор Java.


3
Ну, разве не все в Java и C # все о синтаксическом сахаре для байт-кодов JVM или CLR? :) Просто
говори

2

Перечисления Java позволяют легко преобразовывать типы из имени, используя сгенерированный компилятором метод valueOf, т.е.

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

Эквивалент необработанного перечисления в C # более многословен:

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

Однако, если вы идете по маршруту, предложенному Кентом, вы можете легко реализовать ValueOf метод в своем классе enum.


Пример Java использует синтетический метод, сгенерированный компилятором - вообще не имеет ничего общего с обобщениями. У Enum есть универсальный метод valueOf, но он использует дженерики Class, а не Enum.
Том Хотин - tackline

2

Я подозреваю, что перечисления в C # являются только внутренними константами CLR, но не настолько знакомы с ними. Я декомпилировал некоторые классы в Java, и я могу сказать, что вы хотите, чтобы Enums были после конвертации.

Ява делает что-то подлое. Он рассматривает класс enum как обычный класс, который, насколько я могу судить, использует множество макросов при обращении к значениям enum. Если у вас есть оператор case в классе Java, который использует перечисления, он заменяет ссылки перечисления на целые числа. Если вам нужно перейти к строке, он создает массив строк, проиндексированных по порядковому номеру, который он использует в каждом классе. Я подозреваю, чтобы сэкономить на боксе.

Если вы загрузите этот декомпилятор, вы увидите, как он создает свой класс и интегрирует его. Скорее увлекательно, если честно. Раньше я не использовал класс enum, потому что думал, что он раздут только для массива констант. Мне нравится это лучше, чем ограниченный способ их использования в C #.

http://members.fortunecity.com/neshkov/dj.html - декомпилятор Java


0

Перечисление в Java намного сложнее, чем перечисление C #, и, следовательно, более мощно. Поскольку это просто еще один синтаксический сахар времени компиляции, мне интересно, стоило ли включать язык, учитывая его ограниченное использование в реальных приложениях. Иногда сложнее не пускать материал в язык, чем отказаться от давления, чтобы включить незначительную функцию.


2
Я с уважением не согласен. Перечисления Java 1.5 - это мощная языковая функция, которую я много раз использовал для реализации ОО-ориентированного решения проблемы, связанной с дискретным набором именованных констант.
Огр псалом33

1
Может быть, вы сделали. Но помимо умной интеграции языка «коммутатора», остальная функциональность может быть легко воспроизведена в C # или самой Java, как показали приведенные выше примеры.
dmihailescu

@dmihailescu «Тогда enum в Java намного сложнее, чем C #. Поэтому я отбросил Java ...»
Mukus

0
//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

Вы также можете использовать служебный класс для каждого типа перечисления, который содержит экземпляр с расширенными данными для каждого значения перечисления.

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
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.