考虑以下定义:
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
进行哪些改进以使其更安全和/或更高效?
答案 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);
}
你也会回到属性基类中的属性基类,然后必须转换为正确的类型。