构建,迭代和调用具有不同类型的表达式列表

时间:2017-11-09 10:33:47

标签: c# dictionary expression-trees

考虑以下定义:

public class AlternateDescriptionAttribute : Attribute
{
    public string AlternateDescription { get; }

    public AlternateDescriptionAttribute(string s)
    {
        AlternateDescription = s;
    }
}

enum Metasyntactic
{
    [AlternateDescription("Corge")]
    Foo,

    [AlternateDescription("Quux")]
    [Description("Qux")]
    Bar,

    Baz,
}

我想按优先顺序获取这些枚举的属性值,即AlternateDescription>说明> enum.ToString()。换句话说,使用AlternateDescription就在那里,如果没有,则回退到Description,如果两者都不存在,则回退到ToString。

为此,我创建了以下帮助方法:

public static bool TryGetAttributeValue<TAttribute, T>(Enum field, Expression<Func<TAttribute, T>> valueExpression, out T value)
    where TAttribute : Attribute
{
    var attribute = TryGetAttribute<TAttribute>(field);
    if (attribute == null)
    {
        value = default(T);

        return false;
    }

    value = valueExpression.Compile()(attribute);

    return true;
}
正在使用的

static string GetNiceDescription(Enum field)
{
    if (TryGetAttributeValue<AlternateDescription, string>(field, a => a.AlternateDescription, out string alternateDesc))
    {
        return alternateDesc;
    }

    if (TryGetAttributeValue<DescriptionAttribute, string>(field, a => a.Description, out string description))
    {
        return description;
    }

    return field.ToString();
}

然而,这有点笨拙,特别是因为我有超过2个我感兴趣的属性,并且可能在将来更多。我想做的是能够将属性及其相关表达式放入列表中,然后对其进行迭代 - 到目前为止,我已经提出了以下内容:

static string GetNiceDescriptionViaExpressions(Enum field)
{
    Expression<Func<AlternateDescriptionAttribute, string>> exp1 = a => a.AlternateDescription;
    Expression<Func<DescriptionAttribute, string>> exp2 = a => a.Description;
    var expressions = new LambdaExpression[] { exp1, exp2, };

    foreach (var exp in expressions)
    {
        var attributeType = exp.Parameters[0].Type;
        var attributeInstance = field.GetType().GetField(field.ToString()).GetCustomAttributes(attributeType, false).FirstOrDefault();
        if (attributeInstance == null)
        {
            continue;
        }

        var result = exp.Compile().DynamicInvoke(attributeInstance);
        if (result != null)
        {
            return (string)result;
        }
    }

    return field.ToString();
}

但是那不优雅而不是特别编译时安全,我更愿意写下如下内容:

static string GetNiceDescriptionViaExpressions(Enum field)
{
    // attributeExpressionsDictionary would be a dictionary mapping
    // attribute types to expressions - not sure how that would look...
    foreach (var attribute in attributeExpressionsDictionary)
    {
        if (TryGetAttributeValue<attribute.Key, string>(field, attribute.Value, out description))
        {
            return description;
        }
    }

    return field.ToString();
}

这可能吗?如果没有,可以对GetNiceDescriptionViaExpressions进行哪些改进以使其更安全和/或更高效?

2 个答案:

答案 0 :(得分:0)

您是否考虑使用Weight属性的单一属性定义和enum字段的多个注释?

让单个属性负责您的描述会更容易。

属性定义

[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
public sealed class DescriptionAttribute : Attribute
{
    public DescriptionAttribute(int weight, string value)
    {
        this.Weight = weight;
        this.Value = value;
    }

    public int Weight { get; }
    public String Value { get; }
}

Weight属性将用作打破断路器,使用哪种描述。

以下是如何用于注释枚举:

public enum SomeEnum
{
   [Description(1, "Official description"), 
    Description(2, "Alternate Description")]
   Val1,

   [Description(1, "Description")]
   Val2,

   Val3

}

GetDescription方法的实现:

public static String GetDescription<TEnum>(TEnum @enum) where TEnum: struct
        {
            var description = typeof(TEnum)
                .GetFields(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)
                .Single(x => EqualityComparer<TEnum>.Default.Equals((TEnum)x.GetValue(null), @enum))
                .GetCustomAttributes(typeof(DescriptionAttribute), inherit: false)
                .OfType<DescriptionAttribute>()
                .OrderBy(x => x.Weight)
                .Select(x => x.Value)
                .DefaultIfEmpty(@enum.ToString())
                .First();

            return description;
        }

注意:我使用通用版本来避免装入枚举值。对于当前版本,该方法可以与任何结构一起使用,这是不可接受的。我的建议是使用运行时检查,如果传递的值是实际枚举,以避免将来出现问题。

这是一个简单的用法:

// Official description
var fDescription = GetDescription(SomeEnum.Val1);

// Description
var sDescription = GetDescription(SomeEnum.Val2);

// Val3
var tDescription = GetDescription(SomeEnum.Val3);

答案 1 :(得分:0)

我感觉你过于复杂了。我根本没有看到Expression树的用处。如果我理解正确,你的主要问题是关于泛型,而不是表达树。您希望拥有一组具有不同通用参数的对象,并且仍然能够以类型安全的方式访问它们。据我所知,没有办法做到这一点。例如。你总是必须通过对象/基类去某个地方,比如:

public class TestDescription : Attribute
{
    public string Desc {get; set;}
}

public interface ITextExtractor
{
    string GetText(Attribute attribute);
    Attribute GetAttribute(Enum field);
}

public class TextExtractor<TAttribute> : ITextExtractor
    where TAttribute: Attribute
{
    public Func<TAttribute, string> TextGetter {get; private set;}
    public TextExtractor(Func<TAttribute, string> getter){ TextGetter = getter;}
    public Attribute GetAttribute(Enum field)
    {
        return ...;
    }
    public string GetText(Attribute attribute) { return TextGetter((TAttribute)attribute);}
}

然后

    var possibleAttributes = new List<ITextExtractor>{
        new TextExtractor<TestDescription>(a => a.Desc),
        new TextExtractor<DescriptionAttribute>(a => a.Description)};

    foreach (var possibleAttribute in possibleAttributes)
    {
        var attribute = possibleAttribute.GetAttribute(field);
        if (attribute != null) return possibleAttribute.GetText(attribute);

    }

你也会回到属性基类中的属性基类,然后必须转换为正确的类型。