Задача в том, чтобы с наименьшим количеством кода определить немутируемый (immutable) ValueObject с большим количеством свойств.
Использую NHibernate как ORM, поэтому свойства должны быть virtual и public/protected. Этот объект мэпится к таблице из БД.
Этот объект не должен меняться, но надо, чтобы его можно было создать.
Если свойств не много, то проблем нет, например так:
public class ElectricDevice
{
public virtual int Id { get; protected set; }
public virtual string Name { get; protected set; }
public virtual bool IsDecommissioned { get; protected set;}
public ElectricDevice(int id, string name, bool isDecommissioned)
{
Id = id;
Name = name;
IsDecommissioned = isDecommissioned;
}
}
Но если свойств становится много, например, 20, то как-то неудобно, долго писать класс (таких классов к тому же должно быть много). Так же не красиво создавать такой объект через конструктор. Есть какие-нибудь идеи? Может быть в C#6 какой-нибудь новый синтаксис?
Ответ
Если нужен fluent-интерфейс, у меня получилось вот такое решение, основанное на ответе @Stack:
Заводим атрибут, чтобы отмечать классы, к которым нужно строить Builder
namespace BuildCodegen
{
class CreateBuilderAttribute : Attribute { }
}
Помечаем этим атрибутом наш класс:
[CreateBuilder]
public class ElectricDevice
{
public virtual int Id { get; protected set; }
public virtual string Name { get; protected set; }
public virtual bool IsDecommissioned { get; protected set; }
public ElectricDevice(int id, string name, bool isDecommissioned)
{
Id = id;
Name = name;
IsDecommissioned = isDecommissioned;
}
}
Заводим в нашем проекте TextTemplate (в соседнем ответе расписано, как именно это делается), называем его Builders.tt
Для доступа к существующему коду используем CodeModel, а не рефлексию, т. к. у рефлексии есть известные проблемы (несвоевременное обновление, блокировка сборок в памяти).
Итак, помещаем в Builders.tt следующий код:
<# /* hostspecific = true, т. к. мы используем CodeModel Visual Studio */ #>
<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Data" #>
<#@ assembly name="EnvDte" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="EnvDTE" #>
<#@ output extension=".cs" #>
<#
// задаём вручную имя атрибута (как сделать лучше?)
var attributeFullName = "BuildCodegen.CreateBuilderAttribute";
var visualStudio = (EnvDTE.DTE)((this.Host as IServiceProvider)
.GetService(typeof(EnvDTE.DTE)));
var project = (EnvDTE.Project)visualStudio.Solution
.FindProjectItem(this.Host.TemplateFile).ContainingProject;
var codeModel = project.CodeModel;
// получили модель кода:
var classes = codeModel.CodeElements.Cast
namespace <#= namespaceName #>
{
public class <#= builderName #>
{
public <#= className #> Build()
{
return new <#= className #>(<#=
string.Join(", ", properties.Select(p =>
p.LowerName + ": " + p.LowerName))
#>);
}
<#
foreach (var prop in properties)
{
// для каждого из свойств определяем несущее поле и
// fluent-метод его установки
#>
<#= prop.Type #> <#= prop.LowerName #>;
public <#= builderName #> With<#= prop.UpperName #>(<#=
prop.Type #> <#= prop.LowerName #>)
{
this.<#= prop.LowerName #> = <#= prop.LowerName #>;
return this;
}
<#
}
#>
}
}
<#
}
#>
<#+
// вспомогательный метод: получаем рекурсивно список классов
// мы смотрим только внутри пространств имён, но не внутри других классов
// (исключительно из-за лени, ну и нужно для вложенного класса придумать,
// куда же класть builder)
IEnumerable
// ну и мелкий класс-обёртка для свойства
class PropertyDescriptor
{
public readonly string Type;
public readonly string UpperName;
public readonly string LowerName;
public PropertyDescriptor(CodeProperty property)
{
this.Type = property.Type.AsString;
var name = property.Name;
this.UpperName = char.ToUpper(name[0]) + name.Substring(1);
this.LowerName = char.ToLower(name[0]) + name.Substring(1);
}
}
#>
Получаем такой автоматически сгенерированный класс:
namespace BuildCodegen
{
public class ElectricDeviceBuilder
{
public ElectricDevice Build()
{
return new ElectricDevice(id: id, name: name, isDecommissioned: isDecommissioned);
}
int id;
public ElectricDeviceBuilder WithId(int id)
{
this.id = id;
return this;
}
string name;
public ElectricDeviceBuilder WithName(string name)
{
this.name = name;
return this;
}
bool isDecommissioned;
public ElectricDeviceBuilder WithIsDecommissioned(bool isDecommissioned)
{
this.isDecommissioned = isDecommissioned;
return this;
}
}
}
Кстати, сам Entity-класс тоже можно генерировать автоматически.
Комментариев нет:
Отправить комментарий