如何将对象的属性映射到 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 方法应使用反射将构造函数参数名称与现有对象的属性相匹配。

目的是除了带有名称与属性匹配的参数的构造函数之外,不需要向不可变 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);
        }
    }
}