如何将对象的属性映射到 C# 中的构造函数参数以进行自动复制构造
How to map properties of an object to constructor arguments in C# for auto copy construction
我有一个不可变对象。例如下面的简单案例。
class Person {
public string Name {get;}
public int Age {get;}
public Person(string name, int age){
Name = name;
Age = age;
}
}
现在我想要一个通用的扩展方法,例如
public static class ObjectExtensions {
public void T With<T,P>(this T target, Expression<Func<T,P>> selector, P value){
/* some implementatation */
}
}
这样我就可以做到
var person = new Person("brad", 12).With(p=>p.Age,55);
person.Age.Should().Be(55);
With 方法应使用反射将构造函数参数名称与现有对象的属性相匹配。
- 性能不是问题。
- 如果构造函数参数名称与 属性 名称不匹配,运行时失败是可以的。 (roslyn 分析器可以解决这个问题)
- 在浅层克隆之后使用反射来使用私有 setter 设置属性也是不可取的。 (我目前有一个解决方案可以做到这一点)
目的是除了带有名称与属性匹配的参数的构造函数之外,不需要向不可变 class 添加额外的方法。
带有可为空参数的显式 With 方法,例如
class Person {
public string Name {get;}
public int Age {get;}
public Person(string name, int age){
Name = name;
Age = age;
}
public Person With(string? name, int? age){
return new Person(name ?? this.Name, age ?? this.Age);
}
}
虽然优雅不是我为我的用例寻找的解决方案。
这是一个例子。这可能可以改进以缓存类型信息以提高速度,但这是您可以改进的基线。正如我在评论中提到的,这是假设构造函数有一个与 属性 同名的参数,忽略大小写。它还假定只有一个构造函数,但您可以根据需要更新它。
public static T With<T, P>(this T target, Expression<Func<T, P>> selector, P value)
{
var expression = selector.Body as MemberExpression;
if (expression == null)
{
throw new InvalidOperationException();
}
var name = expression.Member.Name;
var constructor = typeof(T).GetConstructors().First();
var args = GetParamaters(target, value, constructor, name);
return (T)Activator.CreateInstance(typeof(T), args.ToArray());
}
private static IEnumerable<object> GetParamaters<T, P>(T target, P value, ConstructorInfo constructor, string name)
{
foreach (var parameterInfo in constructor.GetParameters())
{
if (parameterInfo.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))
{
yield return value;
}
else
{
var property =
typeof(T).GetProperties()
.First(x => x.Name.Equals(parameterInfo.Name, StringComparison.InvariantCultureIgnoreCase));
yield return property.GetValue(target, null);
}
}
}
我有一个不可变对象。例如下面的简单案例。
class Person {
public string Name {get;}
public int Age {get;}
public Person(string name, int age){
Name = name;
Age = age;
}
}
现在我想要一个通用的扩展方法,例如
public static class ObjectExtensions {
public void T With<T,P>(this T target, Expression<Func<T,P>> selector, P value){
/* some implementatation */
}
}
这样我就可以做到
var person = new Person("brad", 12).With(p=>p.Age,55);
person.Age.Should().Be(55);
With 方法应使用反射将构造函数参数名称与现有对象的属性相匹配。
- 性能不是问题。
- 如果构造函数参数名称与 属性 名称不匹配,运行时失败是可以的。 (roslyn 分析器可以解决这个问题)
- 在浅层克隆之后使用反射来使用私有 setter 设置属性也是不可取的。 (我目前有一个解决方案可以做到这一点)
目的是除了带有名称与属性匹配的参数的构造函数之外,不需要向不可变 class 添加额外的方法。
带有可为空参数的显式 With 方法,例如
class Person {
public string Name {get;}
public int Age {get;}
public Person(string name, int age){
Name = name;
Age = age;
}
public Person With(string? name, int? age){
return new Person(name ?? this.Name, age ?? this.Age);
}
}
虽然优雅不是我为我的用例寻找的解决方案。
这是一个例子。这可能可以改进以缓存类型信息以提高速度,但这是您可以改进的基线。正如我在评论中提到的,这是假设构造函数有一个与 属性 同名的参数,忽略大小写。它还假定只有一个构造函数,但您可以根据需要更新它。
public static T With<T, P>(this T target, Expression<Func<T, P>> selector, P value)
{
var expression = selector.Body as MemberExpression;
if (expression == null)
{
throw new InvalidOperationException();
}
var name = expression.Member.Name;
var constructor = typeof(T).GetConstructors().First();
var args = GetParamaters(target, value, constructor, name);
return (T)Activator.CreateInstance(typeof(T), args.ToArray());
}
private static IEnumerable<object> GetParamaters<T, P>(T target, P value, ConstructorInfo constructor, string name)
{
foreach (var parameterInfo in constructor.GetParameters())
{
if (parameterInfo.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))
{
yield return value;
}
else
{
var property =
typeof(T).GetProperties()
.First(x => x.Name.Equals(parameterInfo.Name, StringComparison.InvariantCultureIgnoreCase));
yield return property.GetValue(target, null);
}
}
}