复合属性名称的反射

时间:2014-02-02 11:29:13

标签: c# reflection .net-3.5

我有一个Dictionary<string, object>,它将属性名称保存为字符串,并将其值作为对象。我还有一个Bind方法扩展,通过反射,将该属性名称设置为相应的值:

public static T Bind<T>(this T @this, 
                        Dictionary<string, object> newValues, 
                        params string[] exceptions) where T : class
{
    var sourceType = @this.GetType();
    foreach (var pair in newValues.Where(v => !exceptions.Contains(v.Key)))
    {
        var property = sourceType.GetProperty(pair.Key, 
                                              BindingFlags.SetProperty | 
                                              BindingFlags.Public      | 
                                              BindingFlags.Instance);
        var propType = Nullable.GetUnderlyingType(property.PropertyType) ?? 
                       property.PropertyType;
        property.SetValue(@this, (pair.Value == null) ? null : 
                                 Convert.ChangeType(pair.Value, propType), null);
    }
    return @this;
}

例如,考虑这样一个类:

public class User
{
    public string   Name { get; set; }
    public DateTime Date { get; set; }
}

一切都运行正常,除非我得到一个具有另一个对象的属性名称的类,如下所示:

public class User
{
    public string   Name    { get; set; }
    public DateTime Date    { get; set; }
    public Address  Address { get; set; }
}

public class Address
{
    public string PostalCode { get; set; }
}

所以,如果我尝试发送Name属性名称,那么好,但是我遇到了复合属性名称的问题,比如Address.PostalCode

你能告诉一种处理这种情况的方法吗?

编辑#1:

总结问题:在sourceType.GetProperty("Name", ...)类实例的上下文中正确调用User允许设置其值,但在同一实例中使用sourceType.GetProperty("Address.PostalCode", ...)不起作用。

编辑#2: 一个更完整的例子应该是:

var user   = new User{ Address = new Address() };
var values = new Dictionary<string, object>
{
    { "Name"              , "Sample"       },
    { "Date"              , DateTime.Today },
    { "Address.PostalCode", "12345"        } // Here lies the problem
}
user.Bind(values);

3 个答案:

答案 0 :(得分:0)

我的猜测是Convert.ChangeType仅适用于实现IConvertible的对象。因此,我只需添加一个检查,并且仅在Convert.ChangeType具有实现IConvertible的类型时才使用pair.Value。此外,afaik Convert不使用重载转换运算符,因此每当pair.Value不是结构时,您都可以保存此检查,即

object value;
if (pair.Value == null) {
    value = null;
} else {
    value = pair.Value.GetType().IsStruct ? Convert.ChangeType(pair.Value, propType) : pair.Value;
}
...

答案 1 :(得分:0)

核心.NET中有许多绑定引擎,WPFASP.NET MVCwinforms,谁知道其他多少,你可以查看所有源代码和文档关于他们的语法。

让我们看看最简单的案例。假设变量X包含一个对象,并且您具有绑定表达式“A.B.C”。让我们分开绑定路径,第一部分是“A”。因此,您使用反射来获取X中名为“A”的属性,并将其他对象放入X.现在是第二部分“B”,所以让我们在(新)X中找到名为“B”的属性。你找到了,然后把它放到X.现在你到达最后一部分,“C”,现在你可以在X中读取或写入该属性。关键是你不需要递归或任何东西,它只是一个简单的循环,你迭代绑定表达式的各个部分,评估它们,并将当前对象保存在同一个变量中。

但事实是它可以变得更加复杂。您可以要求进行数组索引,例如“A.B [2] .C”。或者如果你有一个路径“A.B”,而X.A为空,你会怎么做?实例化X.A,但如果缺少公共无参数构造函数会怎样?

我希望你看到它可能是一个非常复杂的问题。您必须指定语法和规则,然后实现它。您没有在问题中指定要使用的确切语法和规则。如果它碰巧不仅仅是我上面提到的简单案例,那么解决方案可能过于冗长。

答案 2 :(得分:0)

我能够解决它,确定属性名称是否有句号并重复出现:

public static T Bind<T>(this T @this,
                Dictionary<string, object> newValues,
                params string[] exceptions) where T : class
{
    var sourceType = @this.GetType();
    var binding = BindingFlags.Public | BindingFlags.Instance;
    foreach (var pair in newValues.Where(v => !exceptions.Contains(v.Key)))
    {
        if(pair.Key.Contains("."))
        {
            var property = sourceType.GetProperty(
                           pair.Key.Split('.').First(),
                           binding | BindingFlags.GetProperty);
            var value = property.GetValue(@this, null);
            value.Bind(new Dictionary<string, object>
            {
                { 
                    String.Join(".", pair.Key.Split('.').Skip(1).ToArray()),
                    pair.Value
                }
            });
        }
        else
        {
            var property = sourceType.GetProperty(pair.Key, 
                           binding | BindingFlags.SetProperty);
            var propType = Nullable.GetUnderlyingType(property.PropertyType) ??
                           property.PropertyType;
            property.SetValue(@this, (pair.Value == null) ? null :
                     Convert.ChangeType(pair.Value, propType), null);
        }
    }
    return @this;
}

用法:

var user   = new User {Address = new Address{ User = new User() }};
var values = new Dictionary<string, object>()
{
    {"Name", "Sample"},
    {"Date", DateTime.Today},
    {"Address.PostalCode", "12345"},
    {"Address.User.Name", "Sub Sample"}
};

user.Bind(values);

public class User
{
    public string   Name     { get; set; }
    public DateTime Date     { get; set; }
    public Address  Address  { get; set; }
}

public class Address
{
    public string PostalCode { get; set; }
    public User   User       { get; set; }
}