Eva*_*sen 5 c# generics expression fluent-interface
我想创建一个流畅的界面,可以像这样使用:
void Main() {
ModelStateMappings.MapDomainModel<Book>().MapViewModel<BookViewModel>()
.Properties(book => book.Author, vm => vm.AuthorsName)
.Properties(book => book.Price, vm => vm.BookPrice);
ModelStateMappings.MapDomainModel<Store>().MapViewModel<StoreViewModel>()
.Properties(store => store.Owner, vm => vm.OwnersName)
.Properties(store => store.Location, vm => vm.Location);
}
Run Code Online (Sandbox Code Playgroud)
我希望最终得到一个看起来像这样的集合:
static class ModelStateaMappings {
private static IList<ModelMappings> mappings;
// other methods in here to get it working
}
class ModelMappings {
public Type DomainModelType {get;set;}
public Type ViewModelType {get;set;}
public IList<PropertyMapping> PropertyMappings {get;set;}
}
class PropertyMapping {
public Expression<Func<object, object>> DomainProperty {get;set;}
public Expression<Func<object, object>> ViewModelProperty {get;set;}
}
Run Code Online (Sandbox Code Playgroud)
我无法完成上述工作,但我确实创建了类似的工作方式,但我并不特别喜欢如何设置流畅的界面.我宁愿让它读起来像我上面的方式.
创建流畅界面有两种常见方法。
一种方法是添加到正在构建的类的当前实例并this
从每个方法返回。
像这样的东西:
public class NamesBuilder
{
private List<string> _names = new List<string>();
public NamesBuilder AddName(string name)
{
_names.Add(name);
return this;
}
}
Run Code Online (Sandbox Code Playgroud)
这种构建器的问题是您可以轻松编写有错误的代码:
var namesBuilder = new NamesBuilder();
var namesBuilder1 = namesBuilder.AddName("John");
var namesBuilder2 = namesBuilder.AddName("Jack");
Run Code Online (Sandbox Code Playgroud)
如果我看到这段代码,我会期望namesBuilder1
每个namesBuilder2
代码都只有一个名称,而那个代码则namesBuilder
没有任何名称。然而,该实现将在所有三个变量中具有两个名称,因为它们是同一实例。
实现流畅接口的更好方法是在延迟评估的构建器类上创建一个链,以便在完成构建后创建最终的类。这样,如果您在构建过程中进行分支,就不会犯错误。
这是我希望编写的代码:
var bookMap =
ModelStateMappings
.Build<Book, BookViewModel>()
.AddProperty(book => book.Author, vm => vm.AuthorsName)
.AddProperty(book => book.Price, vm => vm.BookPrice)
.Create();
var bookStore =
ModelStateMappings
.Build<Store, StoreViewModel>()
.AddProperty(store => store.Owner, vm => vm.OwnersName)
.AddProperty(store => store.Location, vm => vm.Location)
.Create();
Run Code Online (Sandbox Code Playgroud)
实现此功能的代码比“名称”示例稍微复杂一些。
public static class ModelStateMappings
{
public static Builder<M, VM> Build<M, VM>()
{
return new Builder<M, VM>();
}
public class Builder<M, VM>
{
public Builder() { }
public Builder<M, VM> AddProperty<T>(
Expression<Func<M, T>> domainMap,
Expression<Func<VM, T>> viewModelMap)
{
return new BuilderProperty<M, VM, T>(this, domainMap, viewModelMap);
}
public virtual Map Create()
{
return new Map();
}
}
public class BuilderProperty<M, VM, T> : Builder<M, VM>
{
private Builder<M, VM> _previousBuilder;
private Expression<Func<M, T>> _domainMap;
private Expression<Func<VM, T>> _viewModelMap;
public BuilderProperty(
Builder<M, VM> previousBuilder,
Expression<Func<M, T>> domainMap,
Expression<Func<VM, T>> viewModelMap)
{
_previousBuilder = previousBuilder;
_domainMap = domainMap;
_viewModelMap = viewModelMap;
}
public override Map Create()
{
var map = _previousBuilder.Create();
/* code to add current map to Map class */
return map;
}
}
}
Run Code Online (Sandbox Code Playgroud)
这种类型的构建器的另一个优点是您还可以维护强类型的属性字段。
当然,您需要在方法中输入正确的映射代码Create
。
您可以通过以下代码来实现
static class ModelStateMappings
{
public static DomainModelMapping<TDomainModel> MapDomainModel<TDomainModel>()
{
// edit the constructor to pass more information here if needed.
return new DomainModelMapping<TDomainModel>();
}
}
public class DomainModelMapping<TDomainModel>
{
public ViewModelMapping<TDomainModel, TViewModel> MapViewModel<TViewModel>()
{
// edit the constructor to pass more information here if needed.
return new ViewModelMapping<TDomainModel, TViewModel>();
}
}
public class ViewModelMapping<TDomainModel, TViewModel>
{
public ViewModelMapping<TDomainModel, TViewModel>
Properties<TDomainPropertyType, TViewModelPropertyType>(
Expression<Func<TDomainModel, TDomainPropertyType>> domainExpr,
Expression<Func<TViewModel, TViewModelPropertyType>> viewModelExpr)
{
// map here
return this;
}
}
Run Code Online (Sandbox Code Playgroud)
您不必指定所有先前设置的泛型类型,因为它们已经被记住为返回类型的泛型参数。可以跳过方法调用的通用参数,Properties
因为它们将由编译器推断。与object
到处使用 s 相比,您的打字效果更好。
当然这是最简单的版本。您可以在这些类型之间传递更多信息,因为您指定了如何创建下一个必要的类型。
它还使得MapViewModel
不MapDomainModel
首先调用而进行调用变得不可能(一旦您创建了构造函数internal
并关闭了单独的 dll 中的所有内容),这应该是一件好事。