What parameters after the compiler (or others) are available for reusing functionality between structures?

Say I created a struct called Percent . Logically, this makes sense as a struct, because it represents a value and should be passed by value whenever it is used. The structure also contains several properties and static implicit operators. A Percent can handle any percentage, including more than 100%.

Now let's say that I would like to use Percent in applications that are only interested in percentages from 0 to 100%. I decided to create a new structure called ValidPercent , which is almost identical to Percent , with the exception of additional checks, to ensure that ValidPercent will never contain a value above 100% or below 0%.

ValidPercent seems to be a good candidate for inheritance, but structures cannot take advantage of inheritance. Also, it’s not only that you can reset ValidPercent to Percent .

Are there any tools that would allow me to define Percent and ValidPercent without duplicating most of the code used in each of them?

I did a little research on PostSharp and t4 patterns, but I was not able to determine if they can be used for this purpose. Can they or other available tools?

+4
source share
2 answers

T4 can be used for this. This is the best variant? It depends on your scenario (suppose you published a simplified version of your real scenario).

In any case, you can define a template that generates several options. Using partial classes and methods, you can inject specific behavior into the generated code (for example, validation).

Here you can find the full source code: https://github.com/mrange/CodeStack/tree/master/q18861246/TestProject

I am using VS2013, but this works 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 to write supported metaprograms (my preferred term) to separate the Model, that is, what we would like to create from the view, that is, how the model is converted to 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 is mostly similar to ASP / PHP.

 <# // The "View" defines *how* the model is transformed into code foreach (var cls in model) { #> ... 

To inject validation behavior, I injected an extension point into the generated code:

 // Partial methods are great to inject customized behavior into the generated code skeleton static partial void Partial_ValidateValue (decimal value); 

Partial methods basically work as events, but they are included at compile time. Partial_ValidateValue is called before m_value is assigned, ensuring that any class invariants are supported.

To implement the validation behavior, I define another part of the ValidPercent class 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"); } } } 

An operator is just a convenience operator, allowing an implicit conversion from ValidPercent ==> Percent (this is always safe). Partial_ValidateValue performs the actual check.

This should give you a few starting points when you think about whether the T4 suits you.

Hope this helps ...

+1
source

As you stated that structures do not allow inheritance, you can take a look at the transition to the class. Where you can inherit a class and provide validation of values.

This can be done as shown below. Just an idea.

 public class Percent { public static implicit operator double(Percent p) { return p.Value; } private Percent() { } public Percent(double value) { this.Value = value; } double value; public double Value { get { return this.value; } set { if (!ValidateNewValue(value)) throw new ArgumentException(string.Format("The value '{0}' is not a valid.", value)); this.value = value; } } protected virtual bool ValidateNewValue(double value) { return true; } } public class ValidPercent : Percent { public ValidPercent(double d) : base(d) { } protected override bool ValidateNewValue(double value) { return !(value > 100 || value < 0); } } 
0
source

Source: https://habr.com/ru/post/1502700/


All Articles