3

I have an enum as follows. I also have a string in-progress I am trying to parse this string to the appropriate enum. As seen by the following test we want to parse to take a string and return the enum

    [Fact]
    public void TestParseOfEnum()
    {
        var data = "not-started";

        var parsed = EnumExtensions.GetValueFromEnumMember<CarePlan.CarePlanActivityStatus>(data);

        parsed.ShouldBe(CarePlan.CarePlanActivityStatus.NotStarted);
    }

The issue being that enum try parse is checking on the name which means its failing. I need to parse it on this custom attribute.

 CarePlan.CarePlanActivityStatus status
 Enum.TryParse("in-progress", out status)

A try parse will not find this as try parse checks on the name field and not on the custom attribute within this enum. So this would return false and not find my enum

I have been trying to see how I could get a list back of all of the fields within the enum and then test on the literal.

This would work but i have to specify each of the values within the enum in getfield

  var res = typeof(CarePlan.CarePlanActivityStatus)
                 .GetField(nameof(CarePlan.CarePlanActivityStatus.Cancelled))
                 .GetCustomAttribute<EnumLiteralAttribute>(false)
                 .Literal;
            

I tried something like this but Literal doesn't exist at this point apparently so this fails as well.

  var hold = typeof(CarePlan.CarePlanActivityStatus).GetFields().Where(a =>
            a.GetCustomAttributes<EnumLiteralAttribute>(false).Literal.Equals(data));

The enum

The nuget package i am using for the fhir library is Hl7.Fhir.R4

[FhirEnumeration("CarePlanActivityStatus")]
public enum CarePlanActivityStatus
{
  /// <summary>
  /// Care plan activity is planned but no action has yet been taken.
  /// (system: http://hl7.org/fhir/care-plan-activity-status)
  /// </summary>
  [EnumLiteral("not-started", "http://hl7.org/fhir/care-plan-activity-status"), Hl7.Fhir.Utility.Description("Not Started")] NotStarted,
  /// <summary>
  /// Appointment or other booking has occurred but activity has not yet begun.
  /// (system: http://hl7.org/fhir/care-plan-activity-status)
  /// </summary>
  [EnumLiteral("scheduled", "http://hl7.org/fhir/care-plan-activity-status"), Hl7.Fhir.Utility.Description("Scheduled")] Scheduled,
  /// <summary>
  /// Care plan activity has been started but is not yet complete.
  /// (system: http://hl7.org/fhir/care-plan-activity-status)
  /// </summary>
  [EnumLiteral("in-progress", "http://hl7.org/fhir/care-plan-activity-status"), Hl7.Fhir.Utility.Description("In Progress")] InProgress,
  /// <summary>
  /// Care plan activity was started but has temporarily ceased with an expectation of resumption at a future time.
  /// (system: http://hl7.org/fhir/care-plan-activity-status)
  /// </summary>
  [EnumLiteral("on-hold", "http://hl7.org/fhir/care-plan-activity-status"), Hl7.Fhir.Utility.Description("On Hold")] OnHold,
  /// <summary>
  /// Care plan activity has been completed (more or less) as planned.
  /// (system: http://hl7.org/fhir/care-plan-activity-status)
  /// </summary>
  [EnumLiteral("completed", "http://hl7.org/fhir/care-plan-activity-status"), Hl7.Fhir.Utility.Description("Completed")] Completed,
  /// <summary>
  /// The planned care plan activity has been withdrawn.
  /// (system: http://hl7.org/fhir/care-plan-activity-status)
  /// </summary>
  [EnumLiteral("cancelled", "http://hl7.org/fhir/care-plan-activity-status"), Hl7.Fhir.Utility.Description("Cancelled")] Cancelled,
  /// <summary>
  /// The planned care plan activity has been ended prior to completion after the activity was started.
  /// (system: http://hl7.org/fhir/care-plan-activity-status)
  /// </summary>
  [EnumLiteral("stopped", "http://hl7.org/fhir/care-plan-activity-status"), Hl7.Fhir.Utility.Description("Stopped")] Stopped,
  /// <summary>
  /// The current state of the care plan activity is not known.  Note: This concept is not to be used for "other" - one of the listed statuses is presumed to apply, but the authoring/source system does not know which one.
  /// (system: http://hl7.org/fhir/care-plan-activity-status)
  /// </summary>
  [EnumLiteral("unknown", "http://hl7.org/fhir/care-plan-activity-status"), Hl7.Fhir.Utility.Description("Unknown")] Unknown,
  /// <summary>
  /// Care plan activity was entered in error and voided.
  /// (system: http://hl7.org/fhir/care-plan-activity-status)
  /// </summary>
  [EnumLiteral("entered-in-error", "http://hl7.org/fhir/care-plan-activity-status"), Hl7.Fhir.Utility.Description("Entered in Error")] EnteredInError,
}

// example of everything I have tested with

The nuget package i am using for the fhir library is Hl7.Fhir.R4

public static class EnumExtensions
{
    public static T GetValueFromEnumMember<T>(string value) where T : Enum
    {
            {
                
                
                // Doesnt work as .Literal shows as red not valid.
                var hold = typeof(CarePlan.CarePlanActivityStatus).GetFields().Where(a =>
                      a.GetCustomAttributes<EnumLiteralAttribute>(false).Literal.Equals(data));
                
                
                // doesnt work as its returning the string i need it to return the enum
                var res = typeof(CarePlan.CarePlanActivityStatus)
                    .GetField(nameof(CarePlan.CarePlanActivityStatus.Cancelled))
                    .GetCustomAttribute<EnumLiteralAttribute>(false)
                    .Literal;
                
                
                

// neither of the following two work as they are just looping though and i cant find the enum they find.
                foreach (CarePlan.CarePlanActivityStatus status in (CarePlan.CarePlanActivityStatus[])Enum.GetValues(
                             typeof(CarePlan.CarePlanActivityStatus)))
                {
                }

                foreach (string i in Enum.GetNames(typeof(CarePlan.CarePlanActivityStatus)))
                {
                    // var res = typeof(CarePlan.CarePlanActivityStatus)
                    //     .GetField(nameof(CarePlan.CarePlanActivityStatus[i]))
                    //     .GetCustomAttribute<EnumLiteralAttribute>(false)
                    //     .Literal;
                    //
                    // Console.WriteLine($" {res}" );  
                    //
                    // Console.WriteLine($" {i}" );  
                }
                
            }

Isnt there a way to parse a string to an enum without making a large mess of a if statements to test it. Ideally i need to create a generic method as i have about 10 of these enums i need test.

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
  • "I tried something like this but Literal doesn't exist at this point apparently" - it really should. I'd expect you to be able to use `typeof(CarePlanActivityStatus).GetFields().Select(f => (f, f.GetCustomAttribute()))` and go from there. – Jon Skeet Apr 04 '22 at 08:34
  • 3
    (I'd suggest rewriting this as a [mcve] without the summary tags or the Description attributes, along with an example of what you've tried and where it went wrong - that will have a much better signal-to-noise ratio.) – Jon Skeet Apr 04 '22 at 08:35
  • it pretty much is a minimal example. Thats the enum i am using. I am testing with a single console app that's all. The enum is from the [Hl7.Fhir.R4](https://www.nuget.org/packages/Hl7.Fhir.R4/4.0.0-beta2) nuget package. – Linda Lawton - DaImTo Apr 04 '22 at 08:44
  • 1
    I had to massage it a fair bit, such as defining my own `EnumLiteralAttribute` class and removing the `Description` attributes, to get it to build on https://sharplab.io. That means that it's not a [mcve]: I should just be able to copy-paste the code from your question into e.g. sharplab and immediately see your problem. As an 86k rep user you should also know that "this fails as well" is a bad error explanation: what do you see which makes you think that it fails? An exception? Something else? I think there's also an error that `GetCustomAttributes` should be `GetCustomAttribute` – canton7 Apr 04 '22 at 08:46
  • @canton7 there you go. Its the same code i had before. just all in a class hope it helps. There are no exceptions it just does not return the enum – Linda Lawton - DaImTo Apr 04 '22 at 08:59
  • Thanks. "*Doesnt work as .Literal shows as red not valid.*" -- this is because you're calling `GetCustomAttributes` not `GetCustomAttribute`, see my answer. "*doesnt work as its returning the string i need it to return the enum*" -- you're accessing the enum's `Literal` property, which is why you're getting a string. To get the enum value, call `GetValue(null)` on the `FieldInfo`. I've updated my answer to explain this. – canton7 Apr 04 '22 at 09:03

3 Answers3

3

You can try with a extension method to read the Custom Atribute from Enums:

public static class EnumExtensions
{
    public static T GetValueFromEnumMember<T>(string value) where T: Enum
    {
        var type = typeof(T);
        foreach (var field in type.GetFields())
        {
            var attribute = Attribute.GetCustomAttribute(field,
                typeof(EnumMemberAttribute)) as EnumMemberAttribute;
            if (attribute != null)
            {
                if (attribute.Value == value)
                    return (T)field.GetValue(null);
            }
            else
            {
                if (field.Name == value)
                    return (T)field.GetValue(null);
            }
        }
        throw new ArgumentException($"unknow value: {value}");
    }
}

and use it like this:

EnumExtensions.GetValueFromEnumMember<CarePlanActivityStatus>(stringValueToTest)

it returns the Enum Value

J.Salas
  • 1,268
  • 1
  • 8
  • 15
  • 1
    You are a genius i have been fighting with this for an hour. I made one change to your code to test on Litteral and it works. – Linda Lawton - DaImTo Apr 04 '22 at 08:52
  • @DalmTo Note that this won't work if you have multiple attributes on a single field, as indicated in your comment on my answer. – canton7 Apr 04 '22 at 09:10
0

I tried something like this but Literal doesn't exist at this point apparently so this fails as well.

One error in your question is that you're calling GetCustomAttributes rather than GetCustomAttribute. GetCustomAttributes returns an IEnumerable<EnumLiteralAttribute>, which won't of course have a Literal property. GetCustomAttribute returns a single EnumLiteralAttribute (or null), and you can acces its Literal property.

This would produce the error message:

error CS1061: 'IEnumerable<EnumLiteralAttribute>' does not contain a definition for 'Literal' and no accessible extension method 'Literal' accepting a first argument of type 'IEnumerable<EnumLiteralAttribute>' could be found (are you missing a using directive or an assembly reference?)

Another issue is that this will then fail with a NullReferenceException. If you look at what GetFields() is actually returning, the first field it returns is value__, which doesn't have any attributes on it. So your GetCustomAttribute<EnumLiteralAttribute>() returns null, and accessing its Literal member fails with a NullReferenceException.

If you exclude this field from your test, or just make sure that you're safe to GetCustomAttribute returning null, everything works fine.

E.g.:

var hold = typeof(CarePlanActivityStatus).GetFields()
    .Where(a => a.GetCustomAttribute<EnumLiteralAttribute>(false)?.Literal == data);

See it on SharpLab.

If you then want to access the enum which has that attribute, call the FieldInfo's GetValue method:

var hold = typeof(CarePlanActivityStatus).GetFields()
    .FirstOrDefault(a => a.GetCustomAttribute<EnumLiteralAttribute>(false)?.Literal == data)
    ?.GetValue(null);

See it on SharpLab.

From the comments, you can have multiple attributes on a single field, and you want to see if any of their Literal attributes matches data.

In this case you will want to use GetCustomAttributes to get all attributes, but you then want to loop through them and see if the Literal attribute on any of them matches what you want. The easiest way to do this is with Linq's Any:

string data = "cancelled";
var hold = typeof(CarePlanActivityStatus).GetFields()
    .FirstOrDefault(a => a.GetCustomAttributes<EnumLiteralAttribute>(false)
        .Any(x => x.Literal == data))
    ?.GetValue(null);
hold.Dump();

See it on SharpLab.

canton7
  • 37,633
  • 3
  • 64
  • 77
  • the problem was that ?.Literal wasn't even valid. I couldn't use that field. – Linda Lawton - DaImTo Apr 04 '22 at 08:53
  • @DaImTo Then I think the problem was that you were calling `GetCustomAttributes` rather than `GetCustomAttribute`? An error message in your question would clarify this. Updated my answer. – canton7 Apr 04 '22 at 08:53
  • The issue is that there is more then one attribute on the enum so i was using GetCustomAttributes to check them all. Wonder if adding a where would have helped. – Linda Lawton - DaImTo Apr 04 '22 at 09:07
  • The code in your question doesn't have multiple attributes per member? If this can happen, then you'll want to use `GetCustomAttributes`, but you'll want to check whether *any* of them has a `Literal` property with the right value. I.e. `fieldInfo.GetCustomAttributes().Any(x => x.Literal == data)`. I've updated my answer. – canton7 Apr 04 '22 at 09:08
  • GetCustomAttributes was returning a list of two items. I really appreciate your taking the time but the other answer solved the issue. Its working perfectly now. – Linda Lawton - DaImTo Apr 04 '22 at 09:17
  • @DaImTo The other answer won't handle two attributes on the same member, which you've just told me is a concern. I've tried to explain *why* you're getting the errors you were, rather than just giving you a (slightly shoddy) block of code with no explanation. At the least, an upvote would be appreciated if I answer your question :) – canton7 Apr 04 '22 at 09:20
-1

you can use this:

var enumType = typeof(CarePlanActivityStatus);
FieldInfo field = enumType.GetField(nameof(CarePlanActivityStatus.Cancelled));
var enumLiteralAttribute = field.GetCustomAttribute<EnumLiteralAttribute>(false);
WriteLine(enumLiteralAttribute.Name); // canceled
WriteLine(enumLiteralAttribute.Url); // http://hl7.org/fhir/care-plan-activity-status
M Safari
  • 1
  • 2
  • This looks like a slightly wordier version of the first code snippet in the question? – canton7 Apr 04 '22 at 08:49
  • Hi, this code has success result: `WriteLine(enumLiteralAttribute.Name); // canceled WriteLine(enumLiteralAttribute.Url); // http://hl7.org/fhir/care-plan-activity-status` – M Safari Apr 04 '22 at 09:02