Home > database >  How to get type from enum?
How to get type from enum?

Time:11-04

Currently I have this working code

var content = JsonConvert.DeserializeObject<ClassOne>(message.Content);

How do I make the above code dynamic by replacing the hardcoded ClassOne with message.ContentCategory which stores an enum? How do I convert the enum value message.ContentCategory to a type?

public enum ContentCategory
{
    ClassOne, ClassTwo
}

I tried below but no success.

var content = JsonConvert.DeserializeObject<typeof(message.ContentCategory)>(message.Content);

CodePudding user response:

The simplest solution would be a switch statement:

object content;
switch (message.ContentCategory)
{
    case ContentCategory.ClassOne:
        content = JsonConvert.DeserializeObject<ClassOne>(message.Content);
        break;
    case ContentCategory.ClassTwo:
        content = JsonConvert.DeserializeObject<ClassTwo>(message.Content);
        break;
    default:
        throw new ArgumentException($"Unknown category: {message.ContentCategory}", nameof(message));
}

Failing that, you need some way of determining which class (as a Type) the message actually belongs to. If you get that Type then you can use it like this:

Type targetType = DetermineType(message.ContentCategory);
object content = JsonConvert.DeserializeObject(message.Content, targetType);

This uses this non-generic overload of the DeserializeObject method.

DetermineType could look up values from a Dictionary<ContentCategory, Type> that contains types that you manually register when the application starts, or it could find a Type with that name in the loaded assemblies, etc. It's up to you how that aspect should work.

An example implementation of the DetermineType method that scans all namespaces for types declared in the namespace "MyProject.DeserializableTypes" and below, whose name matches the category name:

public static Type DetermineType(ContentCategory category)
{
    return AppDomain.CurrentDomain.GetAssemblies()
        .SelectMany(a => a.GetTypes())
        .Where(a => a.IsClass && !a.IsAbstract && (a.Namespace   ".").StartsWith("MyProject.DeserializableTypes.", StringComparison.Ordinal))
        .FirstOrDefault(t => t.Name == category.ToString());
}

Personally I would prefer registering types in some way because there's less room for error that way IMHO.


An alternative approach might be to create a custom attribute that allows you to specify the target type on the enum:

[AttributeUsage(AttributeTargets.Field)]
public class TargetTypeAttribute : System.Attribute
{
    public TargetTypeAttribute(Type targetType)
    {
        TargetType = targetType;
    }

    public Type TargetType { get; }
}

public enum ContentCategory
{
    [TargetType(typeof(ClassOne))]
    ClassOne,
    [TargetType(typeof(ClassTwo))]
    ClassTwo
}

And then you can lazily build a table of enumValue->Type to use as the DetermineType method:

private static readonly Lazy<IReadOnlyDictionary<ContentCategory, Type>> _targetTypes = new Lazy<IReadOnlyDictionary<ContentCategory, Type>>(BuildTypesDictionary);

private static IReadOnlyDictionary<ContentCategory, Type> BuildTypesDictionary()
{
    var dict = new Dictionary<ContentCategory, Type>();
    var enumValues = Enum.GetValues<ContentCategory>();
    foreach (var value in enumValues)
    {
        Type targetType = typeof(ContentCategory).GetField(value.ToString())?.GetCustomAttribute<TargetTypeAttribute>()?.TargetType;
        if (targetType == null)
        {
            throw new Exception($"Enum {nameof(ContentCategory)} doesn't declare a target type for {value}.");
        }
        dict.Add(value, targetType);
    }
    return dict;
}

public static Type DetermineType(ContentCategory category)
{
    if (!_targetTypes.Value.TryGetValue(category, out var type))
    {
        throw new ArgumentException($"Unknown category: {category}", nameof(category));
    }
    return type;
}
  •  Tags:  
  • c#
  • Related