在C#中创建百分比类型

时间:2009-06-05 09:39:18

标签: c#

我的应用程序处理了很多百分比。这些通常以书面形式而不是十进制形式存储在数据库中(50%将存储为50而不是0.5)。还要求在整个应用程序中一致地格式化百分比。

为此,我一直在考虑创建一个名为percentage的结构来封装这种行为。我想它的签名看起来像这样:

public struct Percentage
{
    public static Percentage FromWrittenValue();
    public static Percentage FromDecimalValue();

    public decimal WrittenValue { get; set; }
    public decimal DecimalValue { get; set; }
}

这是否合理?它会认真地封装一些重复多次的逻辑,但这是人们可能理解的简单逻辑。我想我需要尽可能地使这种类型的行为像普通数字一样,但是我担心从小数创建隐式转换,以防这些人进一步混淆。

有关如何实现此类的任何建议?或令人信服的理由不这样做。

7 个答案:

答案 0 :(得分:11)

我实际上对这里对数据质量的傲慢态度有点惊讶。 不幸的是,口语术语“百分比”可以表示两种不同的事物之一:概率和方差。 OP没有指定哪个,但由于通常计算方差,我猜他可能将百分比表示为概率或分数(例如折扣)。

为此目的编写Percentage类的非常好的理由与演示文稿无关,但确保您可以防止那些愚蠢的愚蠢用户执行输入无效的操作像-5和250这样的值。

我正在考虑一个Probability类:一个有效范围严格为[0,1]的数字类型。您可以将该规则封装在一个地方,而不是在37个地方编写这样的代码:

 public double VeryImportantLibraryMethodNumber37(double consumerProvidedGarbage)
 {
    if (consumerProvidedGarbage < 0 || consumerProvidedGarbage > 1)
      throw new ArgumentOutOfRangeException("Here we go again.");

    return someOtherNumber * consumerProvidedGarbage;
 }

相反,你有这个很好的实现。不,这并不是非常明显的改进,但请记住,每次使用此值时,您都在进行价值检查。

 public double VeryImportantLibraryMethodNumber37(Percentage guaranteedCleanData)
 {
    return someOtherNumber * guaranteedCleanData.Value;
 }

答案 1 :(得分:5)

Percentage类不应该关注UI的格式化本身。相反,实现IFormatProviderICustomFormatter来处理格式化逻辑。

至于转换,我会使用标准TypeConverter路由,这将允许.NET正确处理此类,以及一个单独的PercentageParser实用程序类,它将调用委托给{{1在外部代码中更有用。此外,如果需要,您可以提供TypeDescriptorimplicit转换运算符。

当谈到explicit时,除了语义表达之外,我没有看到任何令人信服的理由将简单的Percentage包装成单独的decimal

答案 2 :(得分:4)

我强烈建议您坚持使用此处的double类型(我看不到decimal类型的任何用途,因为实际上似乎不需要基数为10的精度低位小数)。通过在此处创建Percentage类型,您实际上正在执行不必要的封装,并且只是更难以使用代码中的值。如果你使用double,这是故事百分比(在许多其他任务中)的习惯,你会发现在大多数情况下处理BCL和其他代码要好得多。

我可以看到百分比所需的唯一额外功能是能够轻松转换为/从百分比字符串转换。无论如何,这可以使用单行代码完成,如果你想稍微抽象一下,甚至可以使用扩展方法。

转换为百分比字符串:

public static string ToPercentageString(this double value)
{
    return value.ToString("#0.0%"); // e.g. 76.2%
}

从百分比字符串转换:

public static double FromPercentageString(this string value)
{
    return double.Parse(value.SubString(0, value.Length - 1)) / 100;
}

答案 3 :(得分:3)

这似乎是一件合理的事情,但我会重新考虑你的界面,使其更像其他CLR基元类型,例如:类似的东西。

// all error checking omitted here; you would want range checks etc.
public struct Percentage
{
    public Percentage(decimal value) : this()
    {
        this.Value = value
    }

    public decimal Value { get; private set; }

    public static explicit operator Percentage(decimal d)
    {
        return new Percentage(d);
    }

    public static implicit operator decimal(Percentage p)
    {
        return this.Value;
    }

    public static Percentage Parse(string value)
    {
        return new Percentage(decimal.Parse(value));
    }

    public override string ToString()
    {
        return string.Format("{0}%", this.Value);
    }
}

您当然也希望实施IComparable<T>IEquatable<T>以及EqualsGetHashCode等所有相应的运算符和覆盖。您还需要可能还想考虑实现IConvertibleIFormattable接口。

这是很多工作。结构很可能在1000行的某个地方,需要花费几天时间(我知道这是因为它与我几个月前写的Money结构类似)。如果这对您有成本效益,那就去吧。

答案 4 :(得分:2)

这个问题让我想起了金钱课Patterns of Enterprise Application Architecture所说的 - 这个链接可能会让你思考。

答案 5 :(得分:1)

我认为你可能会在这里混淆演示和逻辑。当从数据库中获取它时,我会将百分比转换为十进制或浮点数(0.5),然后让演示文稿处理格式。

答案 6 :(得分:1)

我不会为此创建一个单独的类 - 这只会产生更多的开销。我认为将double变量设置为数据库值会更快。

如果众所周知数据库将百分比存储为50而不是0.5,那么每个人都会理解像part = (percentage / 100.0) * (double)value这样的状态。