Подтвердить что ты не робот

Возможно ли создать иерархические перечисления?

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

Структура данных выглядит следующим образом:

enum Cars
{
   Ford { Corsair, Cortina, Galaxy, GT },
   Ferrari { Testarossa, California, Enzo },
   ...
}

Я хочу вызвать функцию со следующей сигнатурой:

public void BuildCar(Cars car);

Вот так:

BuildCar(Cars.Ferrari.Enzo);

В принципе, я хочу принудить отношения автомобиля/производителя к типу.

4b9b3361

Ответ 1

Я подумал об ответе на это с расширениями и/или ICustomEnum.

Тогда я подумал - есть ли что-то действительно плохое со следующим?: -)

    enum Cars
    {
       Ford_Corsair,
       Ford_Cortina,
       Ford_Galaxy,
       Ford_GT,

       Ferrari_Testarossa,
       Ferrari_California,
       Ferrari_Enzo,
    }

Ваша функция по-прежнему выглядит следующим образом:

public void BuildCar(Cars car);

Ваш вызов выглядит следующим образом:

BuildCar(Cars.Ferrari_Enzo);

Ответ 2

Самостоятельное решение:

class Cars
{
    private static int CurrentId = 0;

    private readonly int id;

    private Cars()
    {
        id = CurrentId;
        CurrentId++;
    }

    public static class Ford
    {
        public static Cars Corsair = new Cars();
        public static Cars Cortina = new Cars();
        public static Cars Galaxy = new Cars();
        public static Cars GT = new Cars();
    }

    public static class Ferrari
    {
        public static Cars Testarossa = new Cars();
        public static Cars California = new Cars();
        public static Cars Enzo = new Cars();
    }

    // Add overrides of Equals and GetHash from id
}

Таким образом вы потеряете все функции, содержащиеся в классе Enums. Но это понятно, потому что вам нужна логика, которая обычно не поддерживается.

Ответ 3

Да, вы можете получить следующее:

BuildCar(Cars.Ferrari.Enzo)

Но вы не получите этого:

enum Cars
{
   Ford { Corsair, Cortina, Galaxy, GT },
   Ferrari { Testarossa, California, Enzo },
   ...
}

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

Работает следующий код:

using System;

namespace _2DEnum
{
    public class Example
    {
        public void BuildCar(Cars car)
        {
            GC.KeepAlive(car);
        }

        public void ExampleUse()
        {
            BuildCar(Cars.Ferrari.Enzo);
        }
    }

    public sealed class Cars
    {
        private readonly string _value;

        private Cars(string value)
        {
            if (ReferenceEquals(value, null))
            {
                    throw new ArgumentNullException("value");
            }
            else
            {
                _value = value;
            }
        }

        public override string ToString()
        {
            return _value;
        }

        public static class Ferrari
        {
            private static readonly Cars _California = new Cars("California");
            private static readonly Cars _Enzo = new Cars("Enzo");
            private static readonly Cars _Testarossa = new Cars("Testarossa");

            public static Cars California
            {
                get { return Ferrari._California; }
            }

            public static Cars Enzo
            {
                get { return Ferrari._Enzo; }
            }

            public static Cars Testarossa
            {
                get { return Ferrari._Testarossa; }
            }
        }

        public static class Ford
        {
            private static readonly Cars _Corsair = new Cars("Corsair");
            private static readonly Cars _Cortina = new Cars("Cortina");
            private static readonly Cars _Galaxy = new Cars("Galaxy");
            private static readonly Cars _GT = new Cars("GT");

            public static Cars Corsair
            {
                get { return Ford._Corsair; }
            }

            public static Cars Cortina
            {
                get { return Ford._Cortina; }
            }

            public static Cars Galaxy
            {
                get { return Ford._Galaxy; }
            }

            public static Cars GT
            {
                get { return Ford._GT; }
            }
        }
    }
}

Затем вы можете попытаться использовать некоторые шаблоны трансформации текста, чтобы иметь возможность вводить его как перечисление.

Kudos to @Эйфорический... Мне нужен знак переанализа.


Шаблоны текстовой трансформации!

Я создал решение для T4, которое использует файл Enum2D.ttinclude, который содержит код для создания этих 2D-перечислений и файл для каждого из ваших "2D-перечислений", например Cars.tt.

Первый Enum2D.ttinclude:

<#@ import namespace="System" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #><#+
    public string BuildEnum2D
    (
        string namespaceName,
        string enumName,
        IEnumerable<Tuple<string, IEnumerable<string>>> items
    )
    {
        string head =
@"
using System;

namespace @namespace
{
    public sealed class @enum
    {
        private readonly string _value;

        private @enum(string value)
        {
            if (ReferenceEquals(value, null))
            {
                throw new ArgumentNullException(""value"");
            }
            else
            {
                _value = value;
            }
        }

        public override string ToString()
        {
            return _value;
        }";
        head = head.Replace("@namespace", namespaceName);
        head = head.Replace("@enum", enumName);
        StringBuilder sb = new StringBuilder();
        sb.Append(head);
        foreach (var item in items)
        {
            sb.Append(
@"

        public static class " + item.Item1 + 
@"
        {"
                    );
            foreach (var entry in item.Item2)
            {
                sb.Append(
@"
            private static readonly Cars _" + entry + @" = new Cars(""" + entry + 
@""");"
                        );
            }

            foreach (var entry in item.Item2)
            {
                sb.Append(
@"

            public static Cars " + entry + @"
            {
                get { return _" + entry + @"; }
            }"
                        );
            }
            sb.Append(
@"
        }"          );

        }
        sb.Append(
@"
    }
}"
                );
        return "// <auto-generated />" + "\r\n" + sb.ToString();
    }
#>

Как вы можете видеть, этот файл определяет функцию BuildEnum2D, которая имеет единственную цель генерировать код для вашего 2D-перечисления с учетом параметров. Теперь нам нужно только позвонить. Для этого мы будем использовать Cars.tt следующим образом:

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ include file="Enum2D.ttinclude" #>
<#@ output extension=".cs" #><#=
    BuildEnum2D
    (
        "Enum2DTest",
        "Cars",
        new Tuple<string, IEnumerable<string>>[]
        {
            new Tuple<string, IEnumerable<string>>
            (
                "Ferrari",
                new string[]
                {
                    "California",
                    "Enzo",
                    "Testarossa"
                }
            ),
            new Tuple<string, IEnumerable<string>>
            (
                "Ford",
                new string[]
                {
                    "Corsair",
                    "Cortina",
                    "Galaxy",
                    "GT"
                }
            )
        }
    )
#>

Как вы можете видеть, мы включаем файл Enum2D.ttinclude, а затем вызываем функцию Build2DEnum, параметры - это пространство имен, имя перечисления и элементы. Таким образом, вы сможете создать больше этих 2D-перечислений, и, надеюсь, maintinin tt файлов не будет слишком большой болью.

Ура!

Ответ 4

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

enum Car {
  FordCorsair,
  FordCortina,
  FordGalaxy,
  FordGT,
  FerrariTestarossa,
  FerrariCalifornia,
  FerrariEnzo,
  ...
}

public sealed class Ford {
  public const Car Corsair = Car.FordCorsair;
  public const Car Cortina = Car.FordCortina;
  public const Car Galaxy = Car.FordGalaxy;
  public const Car GT = Car.GT;
}

public sealed class Ferrari {
  public const Car Testarossa = Car.FerrariTestarossa;
  public const Car California = Car.FerrariCalifornia;
  public const Car Enzo = Car.FerrariEnzo;
  public const Car GT = Car.GT;
}

...
Car mycar = Ford.Corsair;

Конечно, синтаксические преимущества этого сахара могут не стоить боли, связанной с тем, что фактическое перечисление синхронизировано с постоянными членами класса.

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

Ответ 5

вы можете создать класс Cars, который будет содержать вложенный класс для каждого make, который будет содержать константы для каждой модели