使用反射映射类似对象:对象与目标类型

时间:2018-05-18 01:56:29

标签: c# reflection mapping

我在这里完全失败,尽管我看过多篇SO帖子以及我能想到的任何其他内容。

我的目标是制作一个非常非常简单的映射器。在某些单元测试中,我基本上可以将其用作工具。它不需要复杂或任何东西 - 只需将一个对象的高级原语和字符串值映射到另一个对象。所以基本算法是:

  1. TFrom
  2. 获取所有媒体资源
  3. TTo
  4. 获取所有媒体资源
  5. 获取两者中的所有属性,按名称匹配。
    • 我知道这可能是一个错误,因为他们可能有相同的名字,但是不同的类型,但让我们把它放在一边。这不是我在这里碰到的 - 属性和类型在类之间匹配。
  6. 创建我们可以复制到的TTo实例。
  7. 对于在对象之间映射的每个属性:
    1. from对象
    2. 中获取值
    3. 将值转换为属性的类型
    4. to对象
    5. 上设置值
  8. 问题在于,无论我做什么,无论属性的类型是什么(例如intstring),我都会得到以下结果:

      

    对象与目标类型不匹配。

    以下是我使用的代码:

    public TTo Map<TFrom, TTo>(TFrom from)
    {
        if (from == null) return default;
    
        var fromProps = GetProperties(typeof(TFrom));
        var toProps = GetProperties(typeof(TTo));
    
        // Props that can be mapped from one to the other
        var propsToCopy = fromProps.Intersect(toProps, new PropertyComparer()).ToList();
    
        var returnObject = (TTo)Activator.CreateInstance(typeof(TTo));
    
        foreach (var prop in propsToCopy)
        {
            // Copy the values
            var fromValue = prop.GetValue(from, null);
            var convertedValue = Convert.ChangeType(fromValue, prop.PropertyType);
            prop.SetValue(returnObject, convertedValue, null);
        }
    
        return returnObject;
    }
    
    public PropertyInfo[] GetProperties(Type objectType)
    {
        var allProps = objectType.GetProperties(
            BindingFlags.Public | BindingFlags.Instance);
    
        return allProps.Where(p => p.PropertyType.IsPrimitive ||
            p.PropertyType == typeof(string)).ToArray();
    }
    
    private class PropertyComparer : IEqualityComparer<PropertyInfo>
    {
        public bool Equals(PropertyInfo x, PropertyInfo y)
        {
            return x.Name.Equals(y.Name);
        }
    
        public int GetHashCode(PropertyInfo obj)
        {
            return obj.Name.GetHashCode();
        }
    }
    

    以下是我称之为样本类的一个例子:

    public class Foo 
    {
        public string StringProp { get; set; }
        public int IntProp { get; set; }
    }
    
    public class FooOther
    {
        public string StringProp { get; set; }
        public int IntProp { get; set; }
    }
    
    var foo = new Foo { IntProp = 1, StringProp = "foo" };
    var mappedFoo = Map<Foo, FooOther>(foo);
    

    关于我从Visual Studio中获得的唯一提示来自观察窗口:如果属性类型为string,则监视窗口会将convertedValue的类型报告为{{ 1}}。如果属性类型为object,则观察窗口会报告int

1 个答案:

答案 0 :(得分:4)

您正在使用的PropertyInfo仍然耦合到它所代表的属性所属的类型,因此您无法使用它来设置另一种类型的对象的值而不会出现错误。

以下是行为的简短示例:

public class A {
    public string Id {get;set;}
}
public class B {
    public string Id {get;set;}
}

void Main()
{
    var test = new A() { Id = "Test"};
    var prop = test.GetType().GetProperty("Id");

    var b = (B)Activator.CreateInstance(typeof(B));

    var fromValue = prop.GetValue(test);
    var converted = Convert.ChangeType(fromValue, prop.PropertyType);
    prop.SetValue(b, converted, null); // Exception
}

如果您将PropertyInfo视为A的成员,这是有道理的。要解决此问题,您需要获取特定于您的类型的属性信息。我可以用以下内容修复我的例子:

var propTo = typeof(B).GetProperty(prop.Name);
propTo.SetValue(b, converted, null);
Console.WriteLine(b.Id); // Output: Test

将这些内容整合在一起,如果您将foreach的内容更改为以下内容,则应明确说明:

foreach (var prop in propsToCopy)
{
    // Copy the values
    var fromValue = prop.GetValue(from, null);
    var convertedValue = Convert.ChangeType(fromValue, prop.PropertyType);
    var propTo = typeof(TTO).GetProperty(prop.Name);
    propTo.SetValue(returnObject, convertedValue, null);
}