Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generics, enums and custom attributes - is it possible?

Tags:

c#

generics

Apologies for the amount of code, but it is easier to explain it this way.

I have a custom attribute CustomUserData implemented as follows:

public class CustomUserData : Attribute
{
   public CustomUserData(object aUserData)
   {
      UserData = aUserData;
   }
   public object UserData { get; set; }
}

and an extension method for enums as:

public static class EnumExtensions
{
    public static TAttribute GetAttribute<TAttribute>(this Enum aValue) where TAttribute : Attribute
    {
        Type type = aValue.GetType();
        string name = Enum.GetName(type, aValue);
        return type.GetField(name)
                   .GetCustomAttributes(false)
                   .OfType<TAttribute>()
                   .SingleOrDefault();
    }

    public static object GetCustomUserData(this Enum aValue)
    {
        CustomUserData userValue = GetAttribute<CustomUserData>(aValue);
        return userValue != null ? userValue.UserData : null;
    }
}

I then have a helper class that serializes/deserializes an enum that has custom data associated with it as follows:

public static class ParameterDisplayModeEnumListHelper
{
    public static List<ParameterDisplayModeEnum> FromDatabase(string aDisplayModeString)
    {
        //Default behaviour
        List<ParameterDisplayModeEnum> result = new List<ParameterDisplayModeEnum>();

        //Split the string list into a list of strings
        List<string> listOfDisplayModes = new List<string>(aDisplayModeString.Split(','));

        //Iterate the enum looking for matches in the list
        foreach (ParameterDisplayModeEnum displayModeEnum in Enum.GetValues(typeof (ParameterDisplayModeEnum)))
        {
            if (listOfDisplayModes.FindIndex(item => item == (string)displayModeEnum.GetCustomUserData()) >= 0)
            {
                result.Add(displayModeEnum);
            }
        }

        return result;
    }

    public static string ToDatabase(List<ParameterDisplayModeEnum> aDisplayModeList)
    {
        string result = string.Empty;

        foreach (ParameterDisplayModeEnum listItem in aDisplayModeList)
        {
            if (result != string.Empty)
                result += ",";
            result += listItem.GetCustomUserData();
        }

        return result;
    }
}

however this is specific to ParameterDisplayModeEnum and I have a bunch of enums that I need to treat this way for serialization/deserialization so I would prefer to have a generic such as:

public static class EnumListHelper<TEnum>
{
    public static List<TEnum> FromDatabase(string aDisplayModeString)
    {
        //Default behaviour
        List<TEnum> result = new List<TEnum>();

        //Split the string list into a list of strings
        List<string> listOfDisplayModes = new List<string>(aDisplayModeString.Split(','));

        //Iterate the enum looking for matches in the list
        foreach (TEnum displayModeEnum in Enum.GetValues(typeof (TEnum)))
        {
            if (listOfDisplayModes.FindIndex(item => item == (string)displayModeEnum.GetCustomUserData()) >= 0)
            {
                result.Add(displayModeEnum);
            }
        }

        return result;
    }

    public static string ToDatabase(List<TEnum> aDisplayModeList)
    {
        string result = string.Empty;

        foreach (TEnum listItem in aDisplayModeList)
        {
            if (result != string.Empty)
                result += ",";
            result += listItem.GetCustomUserData();
        }

        return result;
    }
}

However this will not work as GetCustomUserData() cannot be invoked. Any suggestions? I cannot change the use of the custom attribute or the use of the enums. I am looking for a generic way to do the serialization/deserialization without having to write a concrete list helper class each time.

All suggestions appreciated.

like image 948
TheEdge Avatar asked Aug 08 '13 03:08

TheEdge


People also ask

Can attributes be generic?

Generic attributes are qualities, skills, and abilities that are valued in study, social situations and employment. They include problem solving ability, teamwork, critical thinking, self-management and basic numeracy.

Can enum have attributes?

An enum can, just like a class , have attributes and methods. The only difference is that enum constants are public , static and final (unchangeable - cannot be overridden).

Can generic type be enum?

On a technical level, there's nothing wrong with using an enum as the type used in a generic type.


2 Answers

Try this code:

public static class EnumListHelper
{
    private static void EnsureIsEnum<TEnum>()
    {
        if (!typeof(TEnum).IsEnum)
            throw new InvalidOperationException(string.Format("The {0} type is not an enum.", typeof(TEnum)));
    }

    public static List<TEnum> FromDatabase<TEnum>(string aDisplayModeString)
        where TEnum : struct
    {
        EnsureIsEnum<TEnum>();
        //Default behaviour
        List<TEnum> result = new List<TEnum>();

        //Split the string list into a list of strings
        List<string> listOfDisplayModes = new List<string>(aDisplayModeString.Split(','));

        //Iterate the enum looking for matches in the list
        foreach (Enum displayModeEnum in Enum.GetValues(typeof(TEnum)))
        {
            if (listOfDisplayModes.FindIndex(item => item == (string)displayModeEnum.GetCustomUserData()) >= 0)
            {
                result.Add((TEnum)(object)displayModeEnum);
            }
        }

        return result;
    }

    public static string ToDatabase<TEnum>(List<TEnum> aDisplayModeList)
        where TEnum : struct
    {
        EnsureIsEnum<TEnum>();
        string result = string.Empty;

        foreach (var listItem in aDisplayModeList.OfType<Enum>())
        {
            if (result != string.Empty)
                result += ",";
            result += listItem.GetCustomUserData();
        }

        return result;
    }
}

var fromDatabase = EnumListHelper.FromDatabase<TestEnum>("test");
EnumListHelper.ToDatabase(fromDatabase);

UPDATE 0

To be clear, because we cannot restrict generics to Enum we should check that the type TEnum is an enum and throw an exception if it is not. When we use the FromDatabase method we know that TEnum is enum, and we can write this code to cast an enum to the specified TEnum:

result.Add((TEnum)(object)displayModeEnum)

in the ToDatabase method we also know that TEnum is enum and we can write this code to convert TEnum to the Enum type:

aDisplayModeList.OfType<Enum>()
like image 112
Vyacheslav Volkov Avatar answered Sep 24 '22 15:09

Vyacheslav Volkov


Ideally you'd want to restrict TEnum to Enum but that won't work as you can not restrict generics to Enum Microsoft
But try following, it might do the trick...

if (listOfDisplayModes.FindIndex(item => 
  item == (string)(displayModeEnum as Enum).GetCustomUserData()) >= 0)
like image 31
Konstantin Avatar answered Sep 24 '22 15:09

Konstantin