Go语言中可打印枚举类型

3

在某些情况下,为了用户交互和调试目的,需要枚举的人类可读字符串表示。到目前为止,我想到的最好方案是:

type ElementType int

const (
    Fire = ElementType(iota)
    Air
    Water
    Earth
)

var elementTypeMap = map[ElementType]string{
    Fire: "The Fiery Fire",
    Air: "The Airy Air",
    Water: "The Watery Water",
    Earth: "The Earthy Earth",
}

func (el ElementType) String() string {
    return elementTypeMap[el]
}

上述内容允许我将枚举用作整数并传递,保持其标准性能,并在任何地方轻松打印其字符串表示。唯一的缺点是如果您有多个枚举类型,则会添加一定量的样板代码:我宁愿避免这种情况而感到非常高兴。
是否有一种方式,最好是惯用的方式,可以减少上述样板代码?

为什么不使用数组?当键值为0、1、2时,使用映射看起来相对复杂。 - undefined
一个数组也可以使用,但总的来说,我想保持自由来分配非序列键(例如偶尔使用位逻辑)。此外,虽然枚举在我的用途中可能会被多次使用(最多达到百万次),但字符串仅用于某种人类可读输出(最多0到20次) - 我不认为这会成为性能问题。 - undefined
在犹豫了一段时间后,我接受了dystroy的答案,因为我的主要目标是尽量减少样板代码,对安全性和性能有适度的考虑。谢谢tux21b和Jsor:在某些情况下,我可能仍然会使用你们的方法,并在我达到足够的声望时为你们的回答点赞。 - undefined
我为你点赞了tux的答案 ;) - undefined
3个回答

4
这看起来更干燥(和更快):
type ElementType int

const (
    Fire = ElementType(iota)
    Air
    Water
    Earth
)

var elementnames = [...]string {
    "The Fiery Fire",
    "The Airy Air",
    "The Watery Water",
    "The Earthy Earth"
}

func (el ElementType) String() string {
    return elementnames[el]
}

请注意,golang-nuts上曾经讨论过是否提供一种通用的解决方案来为枚举常量指定名称,据我所知,这并不被视为必要的(参见https://groups.google.com/forum/#!topic/golang-nuts/fCdBSRNNUY8)。

谢谢dystroy,如果我爱挑剔的话,你忘了类型定义哦 :-)我想对于大多数枚举,我会采用你的解决方案,并且只在不能简单地使用iota时才保留映射。还要感谢你提供的讨论链接。 - undefined
@Francesco 包括在内。但是我并没有期望有人仅仅通过比较行数来评判,所以我认为这并不重要 :) - undefined
抱歉,目前我几乎可以说是一个吹毛求疵的人了。我本来可以帮忙的,但是我觉得插手编辑有点无礼(对于StackOverflow的礼仪还不确定)。 - undefined

3

有其他的方法。例如,使用type elementType struct { name string }var Fire = &elementType{"The Fiery Fire"}。你仍然只需要传递和比较指针,但你可以附加任意数据。

我不确定你的具体用例,但我可能会以类似的方式处理:

type ElementType int

const (
    Fire = ElementType(iota)
    Air
    Water
    Earth
)

func (e ElementType) String() string {
    var names = [...]string{
        Fire:  "The Fiery Fire",
        Air:   "The Airy Air",
        Water: "The Watery Water",
        Earth: "The Earthy Earth",
    }
    return names[e]
}

地图的计算成本相当昂贵。您可以使用数组并仍以任意方式列出元素。另外,将数组设置为局部变量可能会使以后的更改更容易。如果您的ElementType是已导出的,则还应该在那里添加一个if e < 0 || e >= len(names)


是的,你回答中的代码基本上与Francesco发布的代码相同。我只是想列出一些替代的方法,并指出他仍然可以使用数组来标记他的条目。 - undefined
谢谢tux21b。在这种情况下,我猜测名称在每次函数调用时被分配和初始化,而在上述情况下,它是在首次模块导入时进行的。 请注意,如果名称仅用于错误消息和调试,这并不一定是件坏事。 - undefined
重新绑定检查 如果 e < 0 || e >= len(names): 我有一个不太慈善的观点,如果某个开发者费心地显式创建了一个任意的、超出边界的 ElementType,而不是添加到上面的常量中,他们应该遭受恐慌和程序崩溃的命运。 - undefined

1
我认为在这里使用数组和映射并不是最好的方法。你必须手动定义元素,所以我认为switch 是最清晰的选择。像映射一样,而不像数组一样,它可以让你使用非密集常量(即除0,1,2,3...之外的常量)。它还允许你定义一个特殊的 case ,以防万一出现错误并且你使用了一个非常量;至少你可以定义一个自定义的 panic 消息。
func (et ElementType) String() string {
    switch et {
    case Water:
        return "The Watery Water"
    case Air:
        return "The Airy Air"
    case Fire:
        return "The Fiery Fire"
    case Earth:
         return "The Earthy Earth"
    default:
         return "Heart has been banned from the elements"
    }
}

我敢打赌编译器可以对此进行一些高级优化,而对于使用var而不是const声明的数组/映射访问则无法进行这些优化。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接