我知道你说过你不想使用反射,但是在 C# 中使用反射就像(非常未优化的代码)一样简单:
public static void UpdateActualFromDto(object actual, object dto) {
var dtoProps = dto.GetType().GetProperties();
var actualProps = actual.GetType().GetProperties();
var sameProperties = dtoProps.Where(x => actualProps.Select(a => a.Name).Contains(x.Name)).ToDictionary(x => x, x => actualProps.First(p => p.Name == x.Name));
foreach(var (dtoProp, actualProp) in sameProperties) {
var val = dtoProp.GetValue(dto);
if(val != null) actualProp.SetValue(actual, val);
}
}
只是为了好玩,我对其进行了基准测试(同样,这几乎没有优化,您甚至可以拥有 PropertyInfo 的缓存并使其更快):
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-9700K CPU 3.60GHz (Coffee Lake), 1 CPU, 8 logical and 8 physical cores
.NET Core SDK=5.0.102
[Host] : .NET Core 3.1.11 (CoreCLR 4.700.20.56602, CoreFX 4.700.20.56604), X64 RyuJIT [AttachedDebugger]
DefaultJob : .NET Core 3.1.11 (CoreCLR 4.700.20.56602, CoreFX 4.700.20.56604), X64 RyuJIT
| Method |
Mean |
Error |
StdDev |
| CopyNonNull |
899.7 ns |
3.24 ns |
2.71 ns |
基准代码是:
public class Dto {
public string PropertyA { get; set; }
public int? PropertyB { get; set; }
}
public class Actual {
public string PropertyA { get; set; }
public int PropertyB { get; set; }
}
public class CopyDtoBenchmark
{
[Benchmark]
public void CopyNonNull()
{
UpdateActualFromDto(new Actual { PropertyA = "bar", PropertyB = 5 }, new Dto { PropertyA = "foo" });
}
// UpdateActualFromDto method above
}
同样,没有缓存,基准是创建和分配对象(实际对象和 dto)。
只是为了好玩,我添加了一些缓存来匹配 dto/entity 类型,并且时间减少了(对于这个始终使用相同类型的特定基准测试):
| Method |
Mean |
Error |
StdDev |
| CopyNonNull |
322.8 ns |
1.28 ns |
1.20 ns |
更新代码:
private Dictionary<(Type, Type), Dictionary<PropertyInfo, PropertyInfo>> _typeMatchCache =
new();
public void UpdateActualFromDto(object actual, object dto)
{
var dtoType = dto.GetType();
var actualType = actual.GetType();
if (!_typeMatchCache.TryGetValue((dtoType, actualType), out var sameProperties))
{
var dtoProps = dtoType.GetProperties();
var actualProps = actualType.GetProperties();
sameProperties = dtoProps.Where(x => actualProps.Select(a => a.Name).Contains(x.Name)).ToDictionary(x => x, x => actualProps.First(p => p.Name == x.Name));
_typeMatchCache.Add((dtoType, actualType), sameProperties);
}
foreach(var (dtoProp, actualProp) in sameProperties) {
var val = dtoProp.GetValue(dto);
if(val != null) actualProp.SetValue(actual, val);
}
}
同样,这只是一个有趣的实验,如果您要使用这样的代码,您可能需要更好的检查(检查类型是否相同等),但我不会放弃反射说它是如果性能足够好以及使用它的 CPU 成本与编程成本(更重要的是,每次涉及的任何类发生变化时维护),非通用解决方案是否值得.
顺便说一句,我不建议使用它,我可能会选择 Automapper 之类的东西(但会使用反射)