【问题标题】:Which Enum constant will I get if the Enum values are same如果枚举值相同,我将得到哪个枚举常量
【发布时间】:2016-09-12 18:52:20
【问题描述】:

如果有多个具有相同值的枚举常量,我得到的常量是否有逻辑?

我尝试了以下变体,但无法获得合理的逻辑。

主要方法:

public class Program
{
    public static void Main(string[] args)
    {
        Test a = 0;
        Console.WriteLine(a);
    }
}

第一次尝试:

enum Test
{
    a1=0,
    a2=0,
    a3=0,
    a4=0,
}

输出:

a2

第二次尝试:

enum Test
{
    a1=0,
    a2=0,
    a3,
    a4=0,
}

输出:

a4

第三次尝试:

enum Test
{
    a1=0,
    a2=0,
    a3,
    a4,
}

输出:

a2

第四次尝试:

enum Test
{
    a1=0,
    a2=0,
    a3,
    a4
}

输出:

a1

【问题讨论】:

标签: c# .net enums


【解决方案1】:

documentation 实际上解决了这个问题:

如果多个枚举成员具有相同的基础值,并且您尝试根据其基础值检索枚举成员名称的字符串表示形式,您的代码不应对方法将返回哪个名称做出任何假设强>。

(强调)

但是,这并不意味着结果是随机的。这意味着 它是一个可能会发生变化的实现细节。只需一个补丁,实现就可以完全改变,在不同的编译器(MONO、Roslyn 等)之间可能会有所不同,并且在不同的平台上也会有所不同。

如果您的系统设计为要求枚举的反向查找在时间和平台上保持一致,那么不要使用Enum.ToString。要么改变你的设计,让它不依赖于那个细节,要么编写你的自己的方法,保持一致。

因此,您不应编写依赖于该实现的代码,否则您将承担在未来版本中在您不知情的情况下发生更改的风险。

【讨论】:

  • 嗯,是的。但输出不是随机的。例如,第四个的输出总是 a1。所以这背后一定有逻辑。
  • 与其说是“编译器当前是如何实现的”那么逻辑。 MSDN 告诉您,您推断的任何逻辑都不值得信任或依赖。
  • 某些行为看起来像是确定性的。它们可能在 99.9% 的情况下表现完全相同。它们甚至可能确定性的,并且 100% 的时间都以相同的方式行事。但不能保证总是可预测的。最好不要依赖未记录的行为,尤其是当有关于行为的明确警告时。
  • @ParasDPain 我在答案中链接到它。
  • @jackjop 更有可能在六年后新的编译器改变了它“总是”所做的事情,现在你的代码充满了错误,因为你忽略了警告。
【解决方案2】:

TL;DR:枚举的所有字段将通过反射提取,然后插入排序并二进制搜索第一个匹配值。


调用链看起来是这样的:

Enum.Tostring();
Enum.InternalFormat(RuntimeType eT, Object value);
Enum.GetName(Type enumType, Object value);
Type.GetEnumName(object value);

Type.GetEnumName(object value) 是这样实现的:

    public virtual string GetEnumName(object value)
    {
        // standard argument guards...

        Array values = GetEnumRawConstantValues();
        int index = BinarySearch(values, value);

        if (index >= 0)
        {
            string[] names = GetEnumNames();
            return names[index];
        }

        return null;
    }

GetEnumRawConstantValues()GetEnumNames() 都依赖于 GetEnumData(out string[] enumNames, out Array enumValues)

    private void GetEnumData(out string[] enumNames, out Array enumValues)
    {
        Contract.Ensures(Contract.ValueAtReturn<String[]>(out enumNames) != null);
        Contract.Ensures(Contract.ValueAtReturn<Array>(out enumValues) != null);

        FieldInfo[] flds = GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);

        object[] values = new object[flds.Length];
        string[] names = new string[flds.Length];

        for (int i = 0; i < flds.Length; i++)
        {
            names[i] = flds[i].Name;
            values[i] = flds[i].GetRawConstantValue();
        }

        // Insertion Sort these values in ascending order.
        // We use this O(n^2) algorithm, but it turns out that most of the time the elements are already in sorted order and
        // the common case performance will be faster than quick sorting this.
        IComparer comparer = Comparer.Default;
        for (int i = 1; i < values.Length; i++)
        {
            int j = i;
            string tempStr = names[i];
            object val = values[i];
            bool exchanged = false;

            // Since the elements are sorted we only need to do one comparision, we keep the check for j inside the loop.
            while (comparer.Compare(values[j - 1], val) > 0)
            {
                names[j] = names[j - 1];
                values[j] = values[j - 1];
                j--;
                exchanged = true;
                if (j == 0)
                    break;
            }

            if (exchanged)
            {
                names[j] = tempStr;
                values[j] = val;
            }
        }

        enumNames = names;
        enumValues = values;
    }

当跟随时,GetFields(BindingFlags bindingAttr) 会导致 abstract 方法,但在 msdn 上搜索“GetFields”会得到 EnumBuilder.GetFields(BindingFlags bindingAttr)。如果我们遵循它的调用链:

EnumBuilder.GetFields(BindingFlags bindingAttr);
TypeBuilder.GetFields(BindingFlags bindingAttr);
RuntimeType.GetFields(BindingFlags bindingAttr);
RuntimeType.GetFieldCandidates(String name, BindingFlags bindingAttr, bool allowPrefixLookup);
RuntimeTypeCache.GetFieldList(MemberListType listType, string name);
RuntimeTypeCache.GetMemberList<RuntimeFieldInfo>(ref MemberInfoCache<T> m_cache, MemberListType listType, string name, CacheType cacheType);
MemberInfoCache<RuntimeFieldInfo>.GetMemberList(MemberListType listType, string name, CacheType cacheType);
MemberInfoCache<RuntimeFieldInfo>.Populate(string name, MemberListType listType, CacheType cacheType);
MemberInfoCache<RuntimeFieldInfo>.GetListByName(char* pName, int cNameLen, byte* pUtf8Name, int cUtf8Name, MemberListType listType, CacheType cacheType);
MemberInfoCache<RuntimeFieldInfo>.PopulateFields(Filter filter);
// and from here, it is a wild ride...

所以,我将引用Type.GetFields 备注:

GetFields 方法不按特定顺序返回字段,例如字母顺序或声明顺序。您的代码不得依赖于返回字段的顺序,因为该顺序会有所不同。

【讨论】:

  • 如果一切都失败了,请阅读手册。如果这也失败了 - 阅读源代码
  • 有趣的是看看这在运行时有多昂贵。我曾假设编译器会在编译时解决它,显然不是
  • 这就是它当前的实现方式。没有什么可以保证它在所有版本、所有平台或所有编译器中都以相同的方式实现。
  • 当 Oracle 数据库版本 10 出来时,他们进行了一些内部优化,其中没有“order by”排序子句但确实有 group by 子句的 SQL 查询不再总是按按列分组。直到第 9 版,它也可以按组排序,但这从未得到保证,并且在升级到 Oracle 10 时很多代码都崩溃了。我知道我的一些人这样做了!
猜你喜欢
  • 2015-08-21
  • 1970-01-01
  • 2021-08-30
  • 1970-01-01
  • 1970-01-01
  • 2011-07-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多