将 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 种不同的实现:
- 为每个不同的方法重新编译
T
。
- 调用
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.
我有两个分机:
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 种不同的实现:
- 为每个不同的方法重新编译
T
。 - 调用
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.