将字符串解析为具有自定义属性的枚举

Parse string to enum with custom attribute

我有一个枚举如下。我还有一个字符串 in-progress 我正在尝试将此字符串解析为适当的枚举。正如下面的测试所见,我们想要解析一个字符串和 return 枚举

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

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

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

问题是 enum try parse 正在检查名称,这意味着它失败了。我需要在这个自定义属性上解析它。

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

尝试解析不会找到它,因为尝试解析检查名称字段而不是此枚举中的自定义属性。所以这会 return false 并且找不到我的枚举

我一直在尝试查看如何获取枚举中所有字段的列表,然后对文字进行测试。

这可行,但我必须在 getfield

的枚举中指定每个值
  var res = typeof(CarePlan.CarePlanActivityStatus)
                 .GetField(nameof(CarePlan.CarePlanActivityStatus.Cancelled))
                 .GetCustomAttribute<EnumLiteralAttribute>(false)
                 .Literal;
            

我试过类似的方法,但此时 Literal 显然不存在,所以也失败了。

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

枚举

我为 fhir 库使用的 nuget 包是 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,
}

// 我用

测试过的所有示例

我为 fhir 库使用的 nuget 包是 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}" );  
                }
                
            }

有没有一种方法可以将字符串解析为枚举,而无需使用大量 if 语句来测试它。理想情况下,我需要创建一个通用方法,因为我有大约 10 个这些枚举需要测试。

您可以尝试使用扩展方法从枚举中读取自定义属性:

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

并像这样使用它:

EnumExtensions.GetValueFromEnumMember<CarePlanActivityStatus>(stringValueToTest)

它 returns 枚举值

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

您问题中的一个错误是您调用的是 GetCustomAttributes 而不是 GetCustomAttributeGetCustomAttributes returns 一个 IEnumerable<EnumLiteralAttribute>,当然不会有 Literal 属性。 GetCustomAttribute returns 单个 EnumLiteralAttribute (或 null),您可以访问其 Literal 属性.

这会产生错误消息:

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?)

另一个问题是这将失败并显示 NullReferenceException。如果您查看 GetFields() 实际返回的内容,它 returns 的第一个字段是 value__,它没有任何属性。因此,您的 GetCustomAttribute<EnumLiteralAttribute>() returns null 和访问其 Literal 成员失败并显示 NullReferenceException.

如果您从测试中排除此字段,或者只是确保您可以安全地 GetCustomAttribute 返回 null,一切正常。

例如:

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

See it on SharpLab.

如果您随后想要访问具有该属性的枚举,请调用 FieldInfoGetValue 方法:

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

See it on SharpLab.

根据评论,您可以在一个字段上有多个属性,并且您想查看它们的任何 Literal 属性是否匹配 data.

在这种情况下,您将希望使用 GetCustomAttributes 获取所有属性,但随后您希望遍历它们并查看 上的 Literal 属性是否为任何 其中符合您的要求。最简单的方法是使用 Linq 的 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.

你可以使用这个:

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