如何在不求助于 .ToString() 的情况下按含义比较两个不同的枚举?

How to compare two different enums by meaning without resorting to .ToString()?

剧透:遗留代码、C#、.NET 4.52

我正在寻找替代解决方案来比较两个不同的枚举值,它们在不同的项目中以不同的顺序声明,但含义和名称相同。 Like EnumDecl1.FirstNameEnumDecl2.FirstName 含义相同,但它们的底层值不同(它们已由不同项目中的不同工作组声明,但通过 Web 服务相互通信)。

在现有的代码库中,他们基本上使用了这个:

if(var1.ToString().Equals(EnumDecl2.FirstName.ToString() 
   || var1.ToString().Equals(EnumDecl2.SecondName.ToString())

在某些情况下调用 ToUpper/ToLower 让事情变得更有趣。现在用一种方法进行几十次这种比较,你就有了一只昆虫爬过路障。

枚举上的 Equals() 不能被覆盖或扩展,所以您还能提出什么其他建议来改进比较(忽略 var1.ToString(),因为它可以放入变量中)。

这是 15 年以上的旧代码,分布在一个庞大的代码库中。我正在寻找逐步改进它的方法。

一种方法是使用字典。例如,如果我们有以下两个枚举:

enum Enum1 { Value1, Value2, Value3 }
enum Enum2 { Value3, Value1, Value2 }

...我们可以这样写:

// You probably want to declare this globally and instantiate only once.
var enumDictionary = new Dictionary<Enum1, Enum2>
{
    { Enum1.Value1, Enum2.Value1 },
    { Enum1.Value2, Enum2.Value2 },
    { Enum1.Value3, Enum2.Value3 }
};

// Create a bogus value of Enum1.
Enum1 e1 = GetEnum1();
// Get the corresponding Enum2 value.
Enum2 e2 = enumDictionary[e1];

可以先做一个方法:

       public bool IsEqual(EnumDecl1 enumDec1, EnumDecl2 enumDec2)
        {
            return enumDec1.ToString() == enumDec2.ToString() ? true : false;
        }

并调用提供您要检查的 2 个值的方法。

if(IsEqual(EnumDecl1.FirstName,EnumDecl2.SecondeName))
{
    do something...
}

您可以使用 switch 语句。

enum Enum1 { Value1, Value2, Value3 }
enum Enum2 { Value3, Value2, Value1 }

switch (enum1)
{
    case Enum1.Value1:
        return enum2 == Enum2.Value1;
    case Enum1.Value2:
        return enum2 == Enum2.Value2;
    case Enum1.Value3:
        return enum2 == Enum2.Value3;
    default:
        return false;
}

我唯一要注意的是,如果您计划向任一枚举添加值,这可能会导致问题出现,因为您还需要记住更新此开关....

您可以结合扩展方法和 Dictionary

的想法
public static class FirstTypeEnumExtensions
{
    private static Dictionary<FirstTypeEnum, SecondTypeEnum> _firstTypeToSecondTypeMap = new Dictionary<FirstTypeEnum, SecondTypeEnum>
    {
        {FirstTypeEnum.First, SecondTypeEnum.First},
        {FirstTypeEnum.Second, SecondTypeEnum.Second},
        {FirstTypeEnum.Third, SecondTypeEnum.Third}
    };

    public static bool IsEquivalentTo(this FirstTypeEnum first, SecondTypeEnum second)
    {
        var success = _firstTypeToSecondTypeMap.TryGet(first, out var mappedSecond);
        if (!success)
            throw new ArgumentException($"{nameof(FirstTypeEnum)} {first} does not have a mapping defined to {nameof(SecondTypeEnum)}", nameof(first));

        return mappedSecond == second;
    }
}

使用扩展时它看起来像这样

if (firstType.IsEquivalentTo(secondType))
{
    ...
}

具有自动填充地图和双向扩展的更新解决方案

   Dictionary<int, int> map1 = new Dictionary<int, int>();
   Dictionary<int, int> map2 = new Dictionary<int, int>();

   // build the maps
   foreach(var e1 in Enum.GetValues(typeof(EnumDecl1)))
      map1.Add((int)e1, ((int)Enum.Parse<EnumDecl2>(e1.ToString())));

   foreach(var e2 in Enum.GetValues(typeof(EnumDecl2)))
      map2.Add((int)e2, ((int)Enum.Parse<EnumDecl1>(e2.ToString())));


   // extension methods to compare them both ways
    public static bool Is(this EnumDecl1 first, EnumDecl2 second)
    {
        var success = map2.TryGetValue((int)second, out var mappedSecond);

        return success && (mappedSecond == (int)first);
    }

    public static bool Is(this EnumDecl2 first, EnumDecl1 second)
    {
        var success = map1.TryGetValue((int)second, out var mappedSecond);

        return success && (mappedSecond == (int)first);
    }

由于这些不同的项目通过 Web 服务进行通信,因此我将通过以下方式解决此类问题:

  1. 确定您最终希望将整个代码库移至的枚举的规范定义。你可能永远不会真正到达那里,但有一个目标是件好事。

  2. 对于单个项目,请确保无论何时从 Web 服务获取数据,都立即将相关值映射到规范枚举。在该项目中,您永远不会将这些枚举转换为字符串(除非出于某种原因需要显示它们)。您可以直接比较枚举。如果您需要将数据发送到另一个网络服务,请将枚举映射到网络服务期望的值。

  3. 当您需要更新另一个项目时,请确保枚举定义与您的规范枚举相匹配,并对输入和输出执行相同的操作。如果你曾经达到用整个代码库完成此操作的地步,你可以将枚举定义移动到共享项目中,以便更容易维护兼容性,你可以摆脱所有映射代码。

我的解决方案是,当需要特定类型的值时,进行转换,并进行必要的操作。

例如 我有 EnumTeamA 值,但我需要调用带有 EnumTeamB 参数的函数。转换是必要的。

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

public enum EnumTeamA
{
    First = 1,
    Second = 2,
    Third = 3,
    Fourth = 4,
    Fifth = 5
}

public enum EnumTeamB
{
    A = 100,
    B = 200,
    C = 300,
    D = 400,
    E = 500
}


public static class EnumExtensions
{
    private static ReadOnlyDictionary<EnumTeamA, EnumTeamB> TeamADictionary = new ReadOnlyDictionary<EnumTeamA, EnumTeamB>(new Dictionary<EnumTeamA, EnumTeamB>
        {
            { EnumTeamA.First, EnumTeamB.A},
            { EnumTeamA.Second, EnumTeamB.B},
            { EnumTeamA.Third, EnumTeamB.C},
            { EnumTeamA.Fourth, EnumTeamB.D},
            { EnumTeamA.Fifth, EnumTeamB.E}
        });
    private static ReadOnlyDictionary<EnumTeamB, EnumTeamA> TeamBDictionary = new ReadOnlyDictionary<EnumTeamB, EnumTeamA>(new Dictionary<EnumTeamB, EnumTeamA>
        {
            { EnumTeamB.A, EnumTeamA.First},
            { EnumTeamB.B, EnumTeamA.Second},
            { EnumTeamB.C, EnumTeamA.Third},
            { EnumTeamB.D, EnumTeamA.Fourth},
            { EnumTeamB.E, EnumTeamA.Fifth}
        });

    public static EnumTeamB Convert(this EnumTeamA key)
    {
        EnumTeamB value;
        bool ok = TeamADictionary.TryGetValue(key, out value);
        return ok ? value : throw new NotImplementedException("Invalid enum value: " + key.GetType().FullName);
    }

    public static EnumTeamA Convert(this EnumTeamB key)
    {
        EnumTeamA value;
        bool ok = TeamBDictionary.TryGetValue(key, out value);
        return ok ? value : throw new NotImplementedException("Invalid enum value: " + key.GetType().FullName);
    }
}

public static class Program
{
    public static void FunctionThatNeedsEnumTeamA(EnumTeamA value)
    {
        // Your custom code 
    }

    public static void FunctionThatNeedsEnumTeamB(EnumTeamB value)
    {
        // Your custom code
    }

    public static void Main()
    {
        // Context 1 - You have EnumTeamA value but need to call a function with EnumTeamB value.
        EnumTeamA enumTeamAValue = EnumTeamA.Fourth;
        FunctionThatNeedsEnumTeamB(enumTeamAValue.Convert());

        // Context 2 - You have EnumTeamB value but need to call a function with EnumTeamA value.
        EnumTeamB enumTeamBValue = EnumTeamB.D;
        FunctionThatNeedsEnumTeamA(enumTeamBValue.Convert());

        Console.ReadLine();
    }
}