T4 can be used for this. Is it the best option? It depends on your scenario (I assume you posted a simplified version of your real scenario).
Anyway, it's possible to define a template which generates a number of variants. Using partial classes and methods you can inject specific behavior into the generated code (such as validation).
You can find the full source code here: https://github.com/mrange/CodeStack/tree/master/q18861246/TestProject
I use VS2013 but this would work fine in VS2008+.
I define a T4 template:
<#
// The model defines *what* we like generated
var model = new []
{
"ValidPercent" ,
"Percent" ,
};
#>
namespace TestProject
{
<#
// The "View" defines *how* the model is transformed into code
foreach (var cls in model)
{
#>
partial struct <#=cls#>
{
// Partial struct/class are great with T4 or any code-generation tool
decimal m_value;
// Partial methods are great to inject customized behavior into the generated code skeleton
static partial void Partial_ValidateValue (decimal value);
public <#=cls#> (decimal value)
{
Partial_ValidateValue (value);
m_value = value;
}
public decimal Value
{
get
{
return m_value;
}
set
{
Partial_ValidateValue (value);
m_value = value;
}
}
public override string ToString ()
{
return Value + "%";
}
}
<#
}
#>
}
It's good practice in order to write maintainable meta-programs (my preferred term) to separate the Model ie what we like to have generated from the View ie how the Model is transformed into code.
In this case the model is very simple:
// The model defines *what* we like generated
var model = new []
{
"ValidPercent" ,
"Percent" ,
};
The view basically just iterates over the model generating the code. T4 basically is like ASP/PHP.
<#
// The "View" defines *how* the model is transformed into code
foreach (var cls in model)
{
#>
...
In order to be able to inject the validation behavior I have inserted into the generated code an extension point:
// Partial methods are great to inject customized behavior into the generated code skeleton
static partial void Partial_ValidateValue (decimal value);
Partial methods basically works like events but they are hooked up in compile-time. Partial_ValidateValue is called before the assignment of m_value making sure any class invariants are uphold.
In order to inject the validation behavior I define another part of the class ValidPercent in a separate file:
partial struct ValidPercent
{
public static implicit operator Percent(ValidPercent vp)
{
return new Percent (vp.Value);
}
static partial void Partial_ValidateValue(decimal value)
{
if (value < 0M || value > 100M)
{
throw new ArgumentException ("value", "value is expected to be in the range 0..100");
}
}
}
The operator is just a convenience operator to allow implicit conversion from ValidPercent ==> Percent (this is always safe). Partial_ValidateValue does the actual validation.
This should give you some starting points when thinking about if T4 is right for you.
I hope it helps...