将 T 保存在内部扩展中

save T in inner extensions

我有两个分机:

public static TModel ObjectToModel<TModel>(this object source, string[] propsToExclude = null)
        where TModel : new()
{
   var dest = new TModel();
   source.CopyPropertiesToObject(dest, propsToExclude);
   return dest;
}

public static bool CopyPropertiesToObject<T, TU>(this T source, TU dest, string[] propsToExclude = null)
{
   propsToExclude = propsToExclude ?? new string[0];
   var sourceProps = typeof(T).GetProperties().Cast<PropertyInfo>();
   var destProps = typeof(TU).GetProperties().Cast<PropertyInfo>().Where(x => x.CanWrite && sourceProps.Any(s => s.Name == x.Name) && !propsToExclude.Any(e => e == x.Name));
   // code
 }

使用:

source.CopyPropertiesToObject(dest); // fine
dest = source.ObjectToModel<TModel>(); // not work

这不起作用,因为 ObjectToModel 方法中的 source 参数是 object 类型 - 所以 CopyPropertiesToObject 方法中的 var sourceProps = typeof(T).GetProperties()... returns 一个空的属性列表。

如何更改 ObjectToModel<TModel> 扩展以使其正常工作?

只需捕获要从中复制的对象的类型作为类型参数。在下面的代码片段中,我添加了与第一个参数类型对应的 TSource 类型参数。

public static TModel ObjectToModel<TSource, TModel>(this TSource source, string[] propsToExclude = null)
    where TModel : new()
{
    var dest = new TModel();

    source.CopyPropertiesToObject(dest, propsToExclude);

    return dest;
}

我认为问题是由于将源装箱为对象而不是实际类型引起的。所以像这样的东西会做映射。

 public static class Extensions
    {
        public static TModel ObjectToModel<TSource, TModel>(this TSource source, string[] propsToExclude = null)
            where TModel : new()
        {
            var dest = new TModel();

            source.CopyPropertiesToObject(dest, propsToExclude);

            return dest;
        }

        public static bool CopyPropertiesToObject<T, TU>(this T source, TU dest, string[] propsToExclude = null)
        {
            try
            {
                propsToExclude = propsToExclude ?? new string[0];

                var sourceProps = typeof (T).GetProperties().Cast<PropertyInfo>().ToArray();
                var destProps =
                    typeof (TU).GetProperties()
                        .Cast<PropertyInfo>()
                        .Where(
                            x =>
                                x.CanWrite && sourceProps.Any(s => s.Name == x.Name) &&
                                propsToExclude.All(e => e != x.Name)).ToArray();

                foreach (var property in destProps)
                {
                    var sourceProperty = sourceProps.FirstOrDefault(c => c.Name == property.Name);
                    if (source != null)
                    {
                        var value = sourceProperty.GetValue(source);
                        property.SetValue(dest, value);
                    }
                }
            }
            catch
            {
                return false;
            }
            return true;
        }
    }

    [TestClass]
    public class Scratch
    {
        [TestMethod]
        public void PropertiesMapAsExpected()
        {
            var model = new Model {Id = 2, Name = "foo"};
            var viewModel = model.ObjectToModel<Model, ViewModel>();
            Assert.AreEqual(model.Id, viewModel.Id);
            Assert.AreEqual(model.Name, viewModel.Name);
        }

        public class Model
        {
            public int Id { get; set; }
            public string Name { get; set; }

            public int NotMapped { get; set; }
        }

        public class ViewModel
        {
            public int Id { get; set; }
            public string Name { get; set; }
        }
    }

请记住,当您想要开始映射具有不同名称、集合、嵌套对象的属性时,尝试动态映射 类 通常会增加复杂性,并且存在一些与反射相关的性能损失,尽管不是和以前一样糟糕。

您可能想看看 automapper,它适用于几乎所有版本的 .net,高度优化和可配置。

所以让我们把它全部分解,好吗。

我们在这里做什么?

显然我们正在将数据从对象 Ao 复制到对象 Bo。更准确地说:我们正在将类型 A 与类型 B 之间相交的属性从对象 Ao 复制到 Bo。如果 属性 不在两种类型中,我们就不会复制。

假设类型 A 和类型 B 在编译时已知。然而:

This does not work because the source argument in the ObjectToModel method is of type object

我们需要什么?

基本上我们需要运行时类型信息,我们需要获取 属性 信息(反射)。请注意,这里的对象没有接口约束(我们只知道它可以被实例化)——基本上这意味着我们只是使用我们知道在 object 中的东西。我们唯一没有的是类型。

为了论证,让我们添加一些代码:

public class Named 
{
  public string Name { get; set; }
}

public class Foo : Named
{
  public int MyProp { get; set; }
}

public class Bar : Named
{
  public int MyProp { get; set; }
  public int MySecondProp { get; set; }
}

// ...

Bar SomeMethod(Named source) 
{
  // code, code, code...
  return source.ObjectToModel<Bar>();
}

这里的问题是:需要发生什么?应该复制MyProp吗?

由于它的定义方式,我会这么认为。我想这也是 object 首先出现在方法签名中的原因。

如何解决?

好吧,如果你这样做,唯一需要做的就是获取类型 Foo 这样我们就可以获取属性。实现相当简单:

public static TModel ObjectToModel<TModel>(this object source, string[] propsToExclude = null)
        where TModel : new()
{
   var dest = new TModel();
   CopyPropertiesToObject(source, dest, propsToExclude);
   return dest;
}


public static bool CopyPropertiesToObject(this object source, object dest, string[] propsToExclude = null)
{
   propsToExclude = propsToExclude ?? new string[0];
   var sourceProps = source.GetType().GetProperties().Cast<PropertyInfo>();
   var destProps = dest.GetType().GetProperties().Cast<PropertyInfo>().Where(x => x.CanWrite && sourceProps.Any(s => s.Name == x.Name) && !propsToExclude.Any(e => e == x.Name));
   // code
}

如您所见,我已经完全从事物中删除了通用 T。作为奖励,GetType 使用 vtable 查找(继承)来获取 source 对象的最派生类型,在本例中是 Foo 而不是 Named.

但是但是...使用泛型不是对性能有好处吗?

@AsadSaeeduddin 表示使用 typeof(T) 比 GetType 'better',因为信息在编译时已知。让我们在这里深入了解细节。

从开发人员的角度来看,如果我们将对象作为通用模板传递,我们确实知道该对象具有什么类型和属性。但是,这不是 .NET 编译器和运行时的工作方式。

Typeof 在 .NET 中作为 ldtoken IL 指令实现,它基本上获取类型标记。在本例中,要获取令牌的是通用参数 T。此时,您必须意识到泛型不是 C++ 模板。更具体地说,代码在每个方法的基础上进行 JIT,使用约束的细节作为使用和调用方法的方式。我会用一个例子来解释。

假设我们有 类 Name、Foo 和 Bar。所有这些 类 都有一个(虚拟/覆盖)方法 'GetName',我们在这样的泛型中调用它:

// Note: if Named was an interface, you should add 'class' as constraint!
public static string FindName<T>(this T source) where T : Named
{
  return source.GetName();
}

在幕后发生的事情是该方法是由 JIT 编译的。此时,我们确定 T 的所有实例都是(大小相等的)指针(因此存在(隐藏的)class 约束)并且它们都是 Named 的实例。它不像模板那样扩展,所以如果我们将它直接翻译成 C++ 等价物,它会像这样工作:

static string FindName(Named* source)
{
  return source->GetName();
}

其实从这里可以推断出编译后的汇编代码其实和下面的没什么区别:

public static string FindName(this Named source)
{
  return source.GetName();
}

这里与问题的唯一 'real' 区别是您可以使用 typeof(T) 转换为 ldtoken。显然,这为每个 T 给出了不同的结果。此时,.NET 可以选择 2 种不同的实现:

  1. 为每个不同的方法重新编译T
  2. 调用 source.GetType() 非虚拟。

我不会在这里详细说明如何实际测试它,但是选项 (2) 是 .NET 团队决定做的。此规则只有一个例外:如果 T 是值类型,则对每种类型 T 的堆栈处理方式的影响不同(记住,您必须将参数放在堆栈上),这意味着必须重新调用该方法-编译。如果您仍然对如何测试它感兴趣,这是我大约一年前的一个问题。

所以泛型和非泛型一样快?

不,不总是。例如,考虑 IEquality<T> 接口:

bool Equals(T other);

一个典型的实现是:

bool Equals(Foo other) { return other.MyProp == MyProp; }

如果你考虑替代方案(object.Equals),它是这样的:

bool Equals(object other) { return other != null && other is Foo && ((Foo)other).MyProp == MyProp; }

请注意,速度上的差异来自于我们可以假设更多的事实,从而简化了编译器必须做的工作。

回到问题

所以让我们进入底线:

  • 在这里使用或不使用泛型对性能没有任何实际影响。
  • 使用泛型为您提供您正在使用的类型,而不是最派生的类型。从签名来看,我希望后者是 objective.