我正在编写一个类似于 Fluent Validator 的库,它将允许对象修改。对于普通类,它工作没有问题,但我尝试用记录编写类似的方法,并偶然发现了一个大问题。
规则的定义与 Fluent Validator 中的相同:
RuleFor(x => x.Name).ForceUppercaseFormat();
方法
ForceUppercaseFormat()
在扩展类中定义,如下所示:
public static ImmutableLogicRuleBuilder<TModel,string> ForceUppercaseFormat<TModel>(this ImmutableLogicRuleBuilder<TModel,string> builder)
where TModel : INamed
{
builder.Apply(new StringToUpperCase(), (model, changedName) =>
{
model = model with { Name = changedName };
return model;
});
return builder;
}
供参考
public interface INamed
{
string Name { get; }
}
有两个问题。一是
Name
没有init
。我一开始没有添加它,因为我会为类和记录重用这个接口。我决定将重点放在记录上,所以我添加了init
。这并没有解决任何问题。根据 Rider 的说法,TModel
不是有效记录,因此我无法使用 with
运算符。
有没有办法限制通用仅适用于记录?
作为一种解决方法,我可以为
ForceUppercaseFormat()
格式添加两个重载,其中 Action<TModel, TProp>
和 Func<TModel, TProp, TModel>
作为类和记录的变体,但这看起来不太好,而且需要很多额外的代码。
// for class
RuleFor(x => x.Name).ForceUppercaseFormat((x, newName) => x.Name = newName);
// for records
RuleFor(x => x.Name).ForceUppercaseFormat((x, newName) => x with {Name = newName});
with
表达式可以与记录类、匿名类型或值类型一起使用。
不存在需要记录类或匿名类型的“通用约束”。在泛型方法中使用 with
的唯一方法是将类型限制为
struct
。 (即使如此,ReSharper 也曾遇到过这样的问题,所以我只能假设 Rider 也是如此。) 可能最简单的选择是定义所有模型都必须实现的突变接口 - 例如:
public interface INamed
{
string Name { get; }
}
public interface INamed<TModel> : INamed where TModel : INamed<TModel>
{
TModel WithName(string nameName);
}
public record Foo(string Name) : INamed<Foo>
{
public Foo WithName(string nameName) => this with { Name = newName };
}
然后您可以将此约束添加到您的方法中:
public static ImmutableLogicRuleBuilder<TModel,string> ForceUppercaseFormat<TModel>(
this ImmutableLogicRuleBuilder<TModel,string> builder)
where TModel : INamed<TModel>
{
builder.Apply(new StringToUpperCase(), (model, changedName) =>
{
model = model.WithName(changedName);
return model;
});
return builder;
}