What post-compiler (or other) options are available for re-using functionality between structs?

StackOverflow https://stackoverflow.com/questions/18861246

  •  29-06-2022
  •  | 
  •  

Question

Say I've created a struct, called Percent. Logically, it makes sense as a struct because it represents a value and should be passed by value whenever used. The struct also contains a few properties and static implicit operators. A Percent can handle any kind of percentage, including those over 100%.

Now, let's say I'd like to be able to use Percent in applications that only care about Percentages between 0-100%. I decide to create a new struct called ValidPercent, which is virtually identical to Percent except with additional checks to ensure that a ValidPercent never contains a value above 100% or below 0%.

ValidPercent seems like a good candidate for inheritance, but structs cannot take advantage of inheritance. Additionally, there is very little value in being able to downcast ValidPercent to Percent.

Are there any tools available that would allow me to define Percent and ValidPercent without duplicating the majority of the code used within each?

I've done some cursory research on PostSharp and t4 templates, but I was unable to determine if they could be used for this purpose. Can they, or are other tools available that can?

Was it helpful?

Solution

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...

OTHER TIPS

As you have stated that structs dont allow for inheritance you may be looking at migrating to a class. Where you can inherit the class and provide checking of values.

This could be done similar to the following. 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);
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top