我可以使用通用函数获得具有给定描述属性的枚举值吗?

Can I get an enum value having a given description attribute using a generic function?

我正在创建一个通用的过滤用户控件,以允许用户在 WPF 应用程序的 CollectionView 上应用各种过滤器。

所以,我有一个充满实体和属性的 CollectionView。因此,我没有为每个实体创建不同的用户控件,而是想出了这个:

foreach (PropertyInfo propertyInfo in _typeMessaging.Mensagem.GetProperties())
{
    var attrs = propertyInfo.GetCustomAttributes(true);
    foreach (object attr in attrs)
    {
        if (attr is DescriptionAttribute descr)
            fields.Add(new FilteringInfo() { Property = propertyInfo, Description = descr.Description }); ;
    }
}

foreach (FilteringInfo filteringInfo in fields.OrderBy(x => x.Property.Name))
{
    Columns.Add(filteringInfo);
}

所以我只需将 Columns 绑定到一个组合框,用户可以 select 他们想根据哪一列(即 属性)过滤他们的视图,我只需要设置我希望用户能够使用描述属性进行过滤的属性。如果 属性 类型是 stringDateTimeintdecimal,用户只需输入他们想要过滤的信息,它就会生成一个过滤器以应用于父 ViewModelCollectionView。然后它 returns 一个 FilteringInfo 对象到父对象 ViewModel,它具有选择的 PropertyInfo 和用户想要过滤的值,前面是过滤词作为参数。

这个FilteringInfo被传递给一个FiltersCollection,它存储了用户请求的所有过滤器,returns一个Filter被添加到CollectionView:

public class FiltersCollection
{
    private readonly GroupFilter _filtros = new();
    public Predicate<object> AddNewFilter(EntityBase entity)
    {
        FilteringInfo filteringInfo = entity as FilteringInfo;
        switch (filteringInfo.FilterInfo.Split(':')[0])
        {
            case "wholefield":
                _filtros.AddFilter(x => x is EntityBase entityBase && ((string)entityBase.GetPropValue(filteringInfo.Property.Name)).Equals(filteringInfo.FilterInfo.Split(':')[1], StringComparison.OrdinalIgnoreCase));
                break;
            case "contains":
                _filtros.AddFilter(x => x is EntityBase entityBase && ((string)entityBase.GetPropValue(filteringInfo.Property.Name)).Contains(filteringInfo.FilterInfo.Split(':')[1], StringComparison.OrdinalIgnoreCase));
                break;
            case "startswith":
                _filtros.AddFilter(x => x is EntityBase entityBase && ((string)entityBase.GetPropValue(filteringInfo.Property.Name)).StartsWith(filteringInfo.FilterInfo.Split(':')[1], StringComparison.OrdinalIgnoreCase));
                break;
            case "datebetween":
                string[] dates = filteringInfo.FilterInfo.Split(':')[1].Split(';');
                DateTime start = DateTime.Parse(dates[0]);
                DateTime end = DateTime.Parse(dates[1]).AddDays(1).AddSeconds(-1);
                _filtros.AddFilter(x => x is EntityBase entityBase && ((DateTime)entityBase.GetPropValue(filteringInfo.Property.Name)).IsBetween(start, end));
                break;
            case "valuebetween":
                string[] valuesBetween = filteringInfo.FilterInfo.Split(':')[1].Split(';');
                decimal startValue = decimal.Parse(valuesBetween[0]);
                decimal endValue = decimal.Parse(valuesBetween[1]);
                _filtros.AddFilter(x => x is EntityBase entityBase && ((decimal)entityBase.GetPropValue(filteringInfo.Property.Name)).IsBetween(startValue, endValue));
                break;
            case "enumvalue":
                _filtros.AddFilter(x => x is EntityBase entityBase && ((Enum)entityBase.GetPropValue(filteringInfo.Property.Name)).Equals(Enum.Parse(filteringInfo.Property.PropertyType, filteringInfo.FilterInfo.Split(':')[1])));
                break;
            case "abovevalue":
                string[] values = filteringInfo.FilterInfo.Split(':')[1].Split(';');
                if (filteringInfo.Property.PropertyType == typeof(int))
                {
                    int headValue = int.Parse(values[0]);
                    _filtros.AddFilter(x => x is EntityBase entityBase && (int)entityBase.GetPropValue(filteringInfo.Property.Name) >= headValue);
                }
                if (filteringInfo.Property.PropertyType == typeof(decimal))
                {
                    decimal headValue = decimal.Parse(values[0]);
                    _filtros.AddFilter(x => x is EntityBase entityBase && (decimal)entityBase.GetPropValue(filteringInfo.Property.Name) >= headValue);
                }
                break;
            case "clearfilters":
                _filtros.RemoveAllFilters();
                return null;
        }
        return _filtros.Filter;
    }
}

组过滤器:

public class GroupFilter
{
    private List<Predicate<object>> _filters;

    public Predicate<object> Filter { get; private set; }

    public GroupFilter()
    {
        _filters = new List<Predicate<object>>();
        Filter = InternalFilter;
    }

    private bool InternalFilter(object o)
    {
        foreach (var filter in _filters)
        {
            if (!filter(o))
            {
                return false;
            }
        }

        return true;
    }

    public void AddFilter(Predicate<object> filter)
    {
        _filters.Add(filter);
    }

    public void RemoveFilter(Predicate<object> filter)
    {
        if (_filters.Contains(filter))
        {
            _filters.Remove(filter);
        }
    }

    public void RemoveAllFilters()
    {
        _filters.Clear();
    }
}

问题是当用户要筛选的 属性 是 enum 时。我可以轻松地使用转换器来填充带有 enum 的描述属性的组合框:

public class EnumDescriptionConverter : IValueConverter
{
    private string GetEnumDescription(Enum enumObj)
    {
        if (enumObj is null) return String.Empty;
        if (Enum.IsDefined(enumObj.GetType(), enumObj) is false) return String.Empty;

        FieldInfo fieldInfo = enumObj.GetType().GetField(enumObj.ToString());

        object[] attribArray = fieldInfo.GetCustomAttributes(false);

        if (attribArray.Length == 0)
        {
            return enumObj.ToString();
        }
        else
        {
            DescriptionAttribute attrib = attribArray[0] as DescriptionAttribute;
            return attrib.Description;
        }
    }

    object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        Enum myEnum = (Enum)value;
        string description = GetEnumDescription(myEnum);
        return description;
    }
}

但是,我很难从给定的描述中得到 Enum。我发现 表明我可以使用 LINQ 遍历 Enum.GetValues(myEnum),但它需要传递我想要评估的枚举,而绑定不需要;据转换器所知,它试图转换回的目标类型只是 Enum.

我尝试传递用于填充可用值的 enums 列表,以便 ConvertBack 可以使用它,但我被告知绑定数据不能用作转换器参数。有什么办法可以做到这一点?如果没有,我还有其他方法可以做到吗?

如果IValueConverter.Convert中的targetType参数真的只是给你Enum,而不是你需要转换成的具体枚举类型,那我觉得转换不了仅来自 Description 值将是可能的。实际上,它可能无论如何都不可靠,因为没有什么可以阻止任何人在同一枚举中创建两个不同的值并为它们提供相同的描述(从而导致歧义)。

这是我的建议:不要 return 使用 string return 自定义 struct。像这样:

public struct EnumValue
{
    public EnumValue(Enum value, string description)
    {
        Value = value;
        Description = description;
    }

    public Enum Value { get; }
    public string Description { get; }

    public override string ToString()
    {
        return Description;
    }
}

返回类似于上面的内容而不只是描述 string 值,将允许您仅通过阅读 Value 属性.[=21 即可转换回枚举值=]

(您还可以更进一步,将检索描述的实际逻辑放入 EnumValue 结构中,从构造函数中删除 description 参数。)