是否可以创建分层枚举?

时间:2012-08-20 04:33:52

标签: c# enums

我想创建一个分层枚举,表示我可以作为参数传递的类型。

数据结构如下所示:

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

我希望使用以下签名调用函数:

public void BuildCar(Cars car);

像这样:

BuildCar(Cars.Ferrari.Enzo);

基本上,我想在类型中强制执行汽车/制造商关系。

5 个答案:

答案 0 :(得分:6)

我考虑过使用扩展程序和/或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);

答案 1 :(得分:5)

自己动手解决方案:

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课程中包含的所有功能。但这是可以理解的,因为您需要通常不支持的逻辑。

答案 2 :(得分:3)

您可以使用枚举来定义所有汽车,然后在每个制造商的类上使用静态成员来返回允许的值:

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;

当然,这对语法糖的好处可能不值得让实际的枚举与常数类成员保持同步。

除此之外,使用字符串常量可能是您的最佳选择。枚举不能嵌套或继承,并且它们不能共享允许在不同名称空间或类中指定相同Enum类型的值的公共接口。

答案 3 :(得分:2)

是的,你可以得到这个:

BuildCar(Cars.Ferrari.Enzo)

但你不会得到这个:

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

你可以做的是一个带有私有构造函数的密封类,它以一种树结构公开intances,你需要一些帮助类。

以下代码有效:

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

然后,您可以尝试使用某些文本转换模板,以便能够像枚举一样输入它。

感谢@ Euphoric ......我需要一个过度分析徽章。


文本转换模板!

我为T4创建了一个解决方案,它使用一个文件Enum2D.ttinclude来保存生成这个2D枚举的代码和每个“2D枚举”的文件,例如Cars.tt。

First 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枚举,并希望在tt文件中维护并不会太痛苦。

干杯!

答案 4 :(得分:0)

您可以创建Cars类,其中包含每个make的嵌套类,其中包含每个模型的consts