子类反射类型错误

时间:2016-11-19 21:11:44

标签: c# reflection

我目前在使用我制作的方法时遇到了一些问题。我使用反射来运行我的类并获得它的所有属性。我使用它将我的模型转换为DTO,反之亦然。

我遇到的问题是,每当我的类有另一个类作为属性时,我都会收到错误。

  

“UserTypeProxy”类型的对象无法转换为“MyNamespace.DTO.UserTypeDto”类型。

这是我的代码:

public static T Cast<T>(object myobj)
{
    Type _objectType = myobj.GetType();
    Type target = typeof(T);

    var x = Activator.CreateInstance(target, false);

    var z = from source in _objectType.GetMembers().ToList()
            where source.MemberType == MemberTypes.Property
            select source;

    var d = from source in target.GetMembers().ToList()
            where source.MemberType == MemberTypes.Property
            select source;

    List<MemberInfo> members = d.Where(memberInfo => d.Select(c => c.Name)
       .ToList().Contains(memberInfo.Name)).ToList();

    PropertyInfo propertyInfo;
    object value;

    foreach (var memberInfo in members)
    {
        propertyInfo = typeof(T).GetProperty(memberInfo.Name);
        var propy = myobj.GetType().GetProperty(memberInfo.Name);
        value = propy.GetValue(myobj, null);

        propertyInfo.SetValue(x, value, null); //<-- this is the line that gives the error
    }
    return (T)x;
}

1 个答案:

答案 0 :(得分:1)

正如之前的一位评论者所说,这不是您应该自己编写/维护的代码。像AutoMapper这样的框架专门用于解决您正在攻击的问题 - 将模型对象转换为DTO。正确的长期选择是开始利用这样一个框架,而不是重新发明轮子。

同时,以下代码是您的问题的短期解决方案。请记住,虽然这可以解决您在问题中提到的特定情况,但对象映射有许多极端情况,最终您将遇到另一个。我建议只使用它作为临时修复,直到您迁移到使用AutoMapper或类似的框架。

根据您的说明和代码,以下是一个模拟您失败的示例:

static void Main(string[] args)
{
    var user = new UserModel
    {
        Name = "User McUserson",
        Age = 30,
        Buddy = new UserModel
        {
            Name = "Buddy McFriendly",
            Age = 28
        }
    };

    // This fails saying that UserModel cannot be converted to UserDto
    var userDto = Cast<UserDto>(user);  
}

class UserModel
{
    public String Name { get; set; }
    public int Age { get; set; }
    public UserModel Buddy { get; set; }
}

class UserDto
{
    public String Name { get; set; }
    public int Age { get; set; }
    public UserDto Buddy { get; set; }
}

问题是Buddy属性与其他属性不同,在模型和DTO类中有不同的类型。 UserModel根本无法分配给UserDto。唯一的例外是如果值为null。

对于类类型的属性,您需要将源类型映射到目标类型,而不是将目标设置为等于源:UserModel - &gt; UserDto。这可以通过递归调用完成。

在我向您展示解决此问题的代码之前,让我们谈谈命名一分钟。调用你的函数Cast()是非常误导的。我们在这里真正做的操作是获取一些源对象并将其属性值映射到特定类型的某个目标对象(对于类类型的属性可能具有递归映射)。

鉴于此术语,这里有一些更新的代码可以解决这个特定的问题:

public static T MapProperties<T>(object source)
{
    return (T)MapProperties(source, typeof(T));
}

public static object MapProperties(object source, Type targetType)
{
    object target = Activator.CreateInstance(targetType, nonPublic: false);
    Type sourceType = source.GetType();

    var sourcePropertyLookup = sourceType.GetProperties().ToDictionary(p => p.Name);
    var targetPropertyLookup = targetType.GetProperties().ToDictionary(p => p.Name);

    var commonProperties = targetPropertyLookup.Keys.Intersect(sourcePropertyLookup.Keys);
    foreach (var commonProp in commonProperties)
    {
        PropertyInfo sourceProp = sourcePropertyLookup[commonProp];
        PropertyInfo targetProp = targetPropertyLookup[commonProp];

        object sourcePropValue = sourceProp.GetValue(source);

        if(sourcePropValue == null || targetProp.PropertyType.IsAssignableFrom(sourceProp.PropertyType))
        {
            targetProp.SetValue(target, sourceProp.GetValue(source));
        }
        else
        {
            object mappedValue = MapProperties(sourceProp.GetValue(source), targetProp.PropertyType);
            targetProp.SetValue(target, mappedValue);
        }
    }

    return target;
}

您可以像使用之前的代码一样使用此功能:

static void Main(string[] args)
{
    var user = new UserModel
    {
        Name = "User McUserson",
        Age = 30,
        Buddy = new UserModel
        {
            Name = "Buddy McFriendly",
            Age = 28
        }
    };

    // This works!
    var userDto = MapProperties<UserDto>(user);  
}

除了一些优化之外,与代码的主要区别在于if-else块。在那里,我们检查是否可以直接将源值分配给目标,在这种情况下,我们执行您的代码到目前为止所执行的操作。否则它假定我们需要以递归方式映射值。这个新部分解决了将模型类类型的源属性转换为DTO类类型的目标属性的问题。