假设
假设我有一个具有属性的类:
class ClassWithProperty
{
public string Prop { get; private set; }
public ClassWithProperty(string prop)
{
this.Prop = prop;
}
}
现在假设我已经创建了该类的实例:
var test = new ClassWithProperty("test value");
我想要什么
我希望打印到控制台:
Prop = 'test value'
一块蛋糕!我使用此代码生成所需的输出:
Console.WriteLine("Prop = '{1}'", test.Prop);
问题
我违反DRY,因为“Prop”在上面的代码中出现两次。如果我重构类并更改属性的名称,我还必须更改字符串文字。如果我有一个包含许多属性的类,那么还有很多样板代码。
建议的解决方案
string result = buildString(() => c.Prop);
Console.WriteLine(result);
buildString方法如下所示:
private static string buildString(Expression<Func<string>> expression)
{
MemberExpression memberExpression = (MemberExpression)expression.Body;
string propertyName = memberExpression.Member.Name;
Func<string> compiledFunction = expression.Compile();
string propertyValue = compiledFunction.Invoke();
return string.Format("{0} = '{1}'", propertyName, propertyValue);
}
问题
上述解决方案运行良好,我很满意,但如果有更简单,更少“可怕”的方法来解决这个问题,那会让我更开心。是否有更简单的替代方法可以用更少和更简单的代码实现相同的结果?也许没有表达树的东西?
修改
根据Manu的好主意(见下文),我可以使用这种扩展方法:
static public IEnumerable<string> ListProperties<T>(this T instance)
{
return instance.GetType().GetProperties()
.Select(p => string.Format("{0} = '{1}'",
p.Name, p.GetValue(instance, null)));
}
这对于获取实例的所有属性的字符串表示非常有用。
但是:从这个枚举中,我怎样才能选择特定属性的类型安全?我会再次使用表达树......或者我没有看到树木的木材?
修改2
在这里使用反射或表达树是一种品味问题。
Luke使用投影初始化器的想法非常棒。根据他的回答,我最终构建了这个扩展方法(基本上只是他的答案的LINQ'd版本):
public static IEnumerable<string> BuildString(this object source)
{
return from p in source.GetType().GetProperties()
select string.Format("{0} = '{1}'", p.Name, p.GetValue(source, null));
}
现在,通话将如下所示:
new { c.Prop }.BuildString().First()
看起来比使用lambda的原始调用好一点(但这也是我猜的味道问题)。然而,Luke的建议优于我的解决方案,因为它允许指定任意数量的属性(见下文)。
答案 0 :(得分:3)
您可以将匿名类型传递给BuildStrings
方法,利用投影初始值设定项自动创建匿名类型属性的名称和值。在方法内部,您将使用反射来查询这些属性。
如果您愿意,这也可以让您传递多个项目。您还可以传递字段和本地以及属性,因为它们都被投影为匿名类型的属性,然后可以使用GetProperties
等进行查询。(当然,该方法实际执行的所有操作都是枚举传入的对象的所有属性。您可以传递任何类型。)
string result = BuildStrings(new { test.Prop }).First();
Console.WriteLine(result);
// or
string foo = "Test";
int bar = 42;
string results = BuildStrings(new { foo, bar, test.Prop });
foreach (string r in results)
{
Console.WriteLine(r);
}
// ...
public static IEnumerable<string> BuildStrings(object source)
{
return source.GetType().GetProperties().Select(
p => string.Format("{0} = '{1}'", p.Name, p.GetValue(source, null)));
}
答案 1 :(得分:3)
我实际上喜欢你使用表达式树的原始想法。这是ET的一个很好的用例。但是你让它看起来有点可怕,因为你编译并执行一个表达式树来获取属性的值,而你所需要的只是名字。使该方法仅返回名称,然后像在第一次“非DRY”尝试中那样使用它。
private static string buildString(Expression<Func<string>> expression)
{
MemberExpression memberExpression = (MemberExpression)expression.Body;
return memberExpression.Member.Name;
}
然后
Console.WriteLine("{0} = {1}", buildString(() => c.Prop), c.Prop);
看起来并不可怕。是的,你在这里使用c.Prop两次,但我认为你得到了显着的性能提升,因为你不需要表达式树编译。而你仍然不使用任何字符串文字。
答案 2 :(得分:2)
var listOfPropertyNamesAndValues = this.GetType().GetProperties()
.Select(prop => string.Format("{0} = '{1}'",
prop.Name, prop.GetValue(this,null)));
如果你想获取一个特定的属性,你必须将它的名字作为字符串传递并通过反射得到它(只需在上面的查询中添加一个where子句)。好消息是你不再侵犯DRY,坏消息是它不是类型安全的。
答案 3 :(得分:1)
答案 4 :(得分:1)
我已经看到它与a delegate and IL interrogation完成了,但这并不简单。简而言之,不:C#中没有infoof
。以下是Eric Lippert对此的看法:In Foof We Trust: A Dialogue