Home > Software engineering >  How to compare two different enums by meaning without resorting to .ToString()?
How to compare two different enums by meaning without resorting to .ToString()?

Time:03-23

Spoiler: legacy code, C#, .NET 4.52

I am looking for alternative solutions to compare two different enum values, that are declared in different projects in a different order but equal in meaning and name. Like EnumDecl1.FirstName has the same meaning as EnumDecl2.FirstName but their underlying values are different (they have been declared by different working groups in different projects but communicate with each other through web services).

Throughout the existing code base they use basically this:

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

In some cases with a call to ToUpper/ToLower to make things more interesting. Now make this comparison a few dozen times in one method and you have an insect crawling over a roadblock.

The Equals() on the enum cant be overridden or extended, so what other suggestions can you come up with to improve comparison (ignore the var1.ToString() as this can be put into a variable).

This is 15 years old code, spread over a huge codebase. I am looking for ways to gradually improve it.

CodePudding user response:

You could combine the ideas of the extension method and the 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;
    }
}

when using the extension it would look like this

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

Updated solution with auto populated maps and bidirectional extensions

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

CodePudding user response:

My solution is that when a value of a specific type is needed, the conversion is made and the necessary operations are carried out.

e.g. I have EnumTeamA value but i need to call a function with EnumTeamB parameter. The conversion is necessary.

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

CodePudding user response:

One way would be to use a dictionary. For example, if we have the following two enums:

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

...we can write something like this:

// 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];

CodePudding user response:

You can first make an method:

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

And calling the method giving the 2 values you want to check.

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

CodePudding user response:

You could use a switch statement.

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

My only note is this could cause issues down the road if you ever plan on adding values to either enum as you would also need to remember to update this switch....

CodePudding user response:

Since these various projects communicate via web services, I would tackle a problem like this in the following manner:

  1. Decide on a canonical definition for the enum that you would eventually like to move the entire code base to. You might not actually ever get there, but it's good to have a target.

  2. For an individual project, make sure that whenever you get data from a web service, you immediately map the relevant values to your canonical enum. In that project you would never convert those enums to strings (unless you needed to display them for some reason). You could compare the enums directly. If you need to send data to another web service, map the enums to the value that web service expects.

  3. When you need to update another project, make sure that the enum definitions match your canonical enums and do the same thing with inputs and outputs. If you ever get to the point where you've done this with the whole code base you can move the enum definition into a shared project to make it easier to maintain compatibility and you could get rid of all the mapping code.

  • Related