4

假设我创建了一个struct名为Percent. 从逻辑上讲,它作为结构是有意义的,因为它表示一个值,并且在使用时应该按值传递。该结构还包含一些属性和静态隐式运算符。APercent可以处理任何类型的百分比,包括超过 100% 的百分比。

现在,假设我希望能够Percent在只关心 0-100% 之间百分比的应用程序中使用。我决定创建一个名为 的新结构,除了额外的检查以确保 ValidPercent 永远不会包含高于 100% 或低于 0% 的值外 ValidPercent,它几乎相同。Percent

ValidPercent似乎是继承的好选择,但结构不能利用继承。此外,能够向下转换ValidPercentPercent.

是否有任何可用的工具可以让我定义 Percent 和 ValidPercent 而无需复制每个工具中使用的大部分代码?

我对 PostSharp 和 t4 模板做了一些粗略的研究,但我无法确定它们是否可以用于此目的。他们可以吗,或者是否有其他可用的工具可以?

4

2 回答 2

1

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

于 2013-09-18T19:14:38.060 回答
0

正如您所说,结构不允许继承,您可能正在考虑迁移到一个类。您可以在哪里继承类并提供值检查。

这可以类似于以下方式完成。只是一个想法。

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);
    }
}
于 2013-09-17T23:14:33.693 回答