C#创建自定义"double"类型

5
在我的应用程序中,我希望所有存储货币金额的属性都能四舍五入到n位小数。
为了代码清晰,我宁愿使用自定义类型MoneyAmount,而不是在所有属性的getter/setter中放置`Math.Round(value, n)`。
有没有一种简洁的方法来实现这个?
我看到了关于重载赋值运算符的这篇文章 - 这是建议的方法吗?
编辑: 考虑到多个视图,我在此发布了我得出的完整代码:
public struct MoneyAmount {
const int N = 4;
private readonly double _value;

public MoneyAmount(double value) {
  _value = Math.Round(value, N);
}

#region mathematical operators
public static MoneyAmount operator +(MoneyAmount d1, MoneyAmount d2) {
  return new MoneyAmount(d1._value + d2._value);
}

public static MoneyAmount operator -(MoneyAmount d1, MoneyAmount d2) {
  return new MoneyAmount(d1._value - d2._value);
}

public static MoneyAmount operator *(MoneyAmount d1, MoneyAmount d2) {
  return new MoneyAmount(d1._value * d2._value);
}

public static MoneyAmount operator /(MoneyAmount d1, MoneyAmount d2) {
  return new MoneyAmount(d1._value / d2._value);
}
#endregion

#region logical operators
public static bool operator ==(MoneyAmount d1, MoneyAmount d2) {
  return d1._value == d2._value;
}
public static bool operator !=(MoneyAmount d1, MoneyAmount d2) {
  return d1._value != d2._value;
}
public static bool operator >(MoneyAmount d1, MoneyAmount d2) {
  return d1._value > d2._value;
}
public static bool operator >=(MoneyAmount d1, MoneyAmount d2) {
  return d1._value >= d2._value;
}
public static bool operator <(MoneyAmount d1, MoneyAmount d2) {
  return d1._value < d2._value;
}
public static bool operator <=(MoneyAmount d1, MoneyAmount d2) {
  return d1._value <= d2._value;
}
#endregion

#region Implicit conversions
/// <summary>
/// Implicit conversion from int to MoneyAmount. 
/// Implicit: No cast operator is required.
/// </summary>
public static implicit operator MoneyAmount(int value) {
  return new MoneyAmount(value);
}

/// <summary>
/// Implicit conversion from float to MoneyAmount. 
/// Implicit: No cast operator is required.
/// </summary>
public static implicit operator MoneyAmount(float value) {
  return new MoneyAmount(value);
}

/// <summary>
/// Implicit conversion from double to MoneyAmount. 
/// Implicit: No cast operator is required.
/// </summary>
public static implicit operator MoneyAmount(double value) {
  return new MoneyAmount(value);
}

/// <summary>
/// Implicit conversion from decimal to MoneyAmount. 
/// Implicit: No cast operator is required.
/// </summary>
public static implicit operator MoneyAmount(decimal value) {
  return new MoneyAmount(Convert.ToDouble(value));
}
#endregion

#region Explicit conversions
/// <summary>
/// Explicit conversion from MoneyAmount to int. 
/// Explicit: A cast operator is required.
/// </summary>
public static explicit operator int(MoneyAmount value) {
  return (int)value._value;
}

/// <summary>
/// Explicit conversion from MoneyAmount to float. 
/// Explicit: A cast operator is required.
/// </summary>
public static explicit operator float(MoneyAmount value) {
  return (float)value._value;
}

/// <summary>
/// Explicit conversion from MoneyAmount to double. 
/// Explicit: A cast operator is required.
/// </summary>
public static explicit operator double(MoneyAmount value) {
  return (double)value._value;
}

/// <summary>
/// Explicit conversion from MoneyAmount to decimal. 
/// Explicit: A cast operator is required.
/// </summary>
public static explicit operator decimal(MoneyAmount value) {
  return Convert.ToDecimal(value._value);
}
#endregion
}

5
附注:在处理"money"时,使用decimal类型更为适合。 - Dmitry Bychenko
4
储存时四舍五入似乎不太妥当。通常只有在显示时才会进行四舍五入。 - Matthew Watson
4
不,实际上每种货币的小数位数都是严格定义的。OP实际上在询问“货币模式”(Money Pattern)。 - Panagiotis Kanavos
1
@MatthewWatson 我知道,这就是为什么Money是一种模式。事实上,它甚至更加复杂,因为有某些规则处理最小化舍入误差(银行家舍入算法只是其中一种技术),以及分配丢失的分数,使得任何东西都不会丢失。其他规则用于管理货币转换,其他规则用于管理每种货币允许的小数位数和内部计算的小数位数。 - Panagiotis Kanavos
@ Martin Mulder:好处是我可以让“MoneyAmount”字段(用于存储等)使用“double”属性,反之亦然! - neggenbe
显示剩余2条评论
2个回答

9
我建议以下操作:
  1. 创建一个名为MoneyAmount的新结构体。
  2. 它包含一个字段:double。
  3. 使用一个double参数的构造函数,此构造函数将其四舍五入,并将其分配给内部字段。
  4. 添加您可能需要的成员/运算符到结构体中,以便它具有与double相同的所有操作,例如+,-等。但是还要考虑从/转换为其他类型的强制转换/转换。每个操作都会产生一个具有四舍五入值的新MoneyAmount实例。
  5. 还应该考虑实现接口IFormattable、IComparable和IConvertible。
短例子如下:
public struct MoneyAmount
{
    const int N = 4;
    private readonly double _value;

    public MoneyAmount(double value)
    {
        _value = Math.Round(value, N);
    }

    // Example of one member of double:
    public static MoneyAmount operator *(MoneyAmount d1, MoneyAmount d2) 
    {
        return new MoneyAmount(d1._value * d2._value);
    }

    /// <summary>
    /// Implicit conversion from double to MoneyAmount. 
    /// Implicit: No cast operator is required.
    /// </summary>
    public static implicit operator MoneyAmount(double value)
    {
        return new MoneyAmount(value);
    }

    /// <summary>
    /// Explicit conversion from MoneyAmount to double. 
    /// Explicit: A cast operator is required.
    /// </summary>
    public static explicit operator double(MoneyAmount value)
    {
        return value._value;
    }

    /// <summary>
    /// Explicit conversion from MoneyAmount to int. 
    /// Explicit: A cast operator is required.
    /// </summary>
    public static explicit operator MoneyAmount(int value)
    {
        return new MoneyAmount(value);
    }

    /// <summary>
    /// Explicit conversion from MoneyAmount to int. 
    /// Explicit: A cast operator is required.
    /// </summary>
    public static explicit operator int(MoneyAmount value)
    {
        return (int)value._value;
    }

    // All other members here...
}

我理解到: double 有很多成员...
有了这些运算符,下面的代码是可能的:
MoneyAmount m = 1.50; // Assignment from a double.
MoneyAmount n = 10; // Assignment from an integer.
m += n; // Mathematical operation with another MoneyAmount .
m *= 10; // Mathematical operation with an integer.
m -= 12.50; // Mathematical operation with a double.

编辑

您可能想要实现的所有转换方法:

  • 显式 MoneyAmount --> int
  • 显式 MoneyAmount --> float
  • 显式 MoneyAmount --> double
  • 显式 MoneyAmount --> decimal

  • 隐式 int--> MoneyAmount

  • 隐式 float --> MoneyAmount
  • 隐式 double--> MoneyAmount
  • 隐式 decimal --> MoneyAmount

您可能想要实现的所有数学运算:

  • MoneyAmount + MoneyAmount
  • MoneyAmount - MoneyAmount
  • MoneyAmount * MoneyAmount
  • MoneyAmount / MoneyAmount

您可能想要实现的所有关系运算:

  • MoneyAmount == MoneyAmount
  • MoneyAmount != MoneyAmount
  • MoneyAmount > MoneyAmount
  • MoneyAmount >= MoneyAmount
  • MoneyAmount < MoneyAmount
  • MoneyAmount <= MoneyAmount

通过这些操作,您已经掌握了所有基础知识。


你能补充一下“所有其他成员”应该是什么吗? - neggenbe
1
记住,你需要重载所有的数学运算符 +-/*><,而且你可能还想允许从常见的数值类型 double, decimal, int 进行显式转换。 - Jamiec
好的,谢谢。如果可以的话,您能否给一个“cast”方法的例子? - neggenbe
1
我在另一个回答中添加了一些细节 @neggenbe。 - Jamiec
一个问题:我该如何绑定到 MoneyAmount 属性? - neggenbe
显示剩余4条评论

4

这很快就变得非常复杂。如@MartinMulder的答案所示,编写struct是很容易的,但要考虑您将想要重载许多组合的运算符,以及包括一些隐式/显式转换。

数学和逻辑运算

考虑您可能希望对MoneyAmount进行数学运算

  • MoneyAmount+MoneyAmount
  • MoneyAmount+double
  • MoneyAmount+int
  • MoneyAmount+decimal

这是+操作符的4个重载。为-/*(以及可能的%)重复此过程。您还需要重载<<===>>=。那就像是30个操作符重载。真费劲!这是很多静态方法。

public static MoneyAmount operator +(MoneyAmount d1, double d2) 
{
    return new MoneyAmount((decimal)(d1._value + d2));
}

显式/隐式转换

现在考虑这段代码的情况

MoneyAmount m = new MoneyAmount(1.234);

你想要做这件事:

MoneyAmount m = 1.234;

这可以通过使用隐式转换运算符来实现。
public static implicit operator MoneyAmount(double d)
{
    return new MoneyAmount((decimal)d);
}

(你需要为每种想要允许隐式转换的类型都需要一个)

另一个:

int i = 4;
MoneyAmount m = (MoneyAmount)i;

这是通过一个明确的强制类型转换运算符重载来实现的。
public static explicit operator MoneyAmount(double d)
{
    return new MoneyAmount((decimal)d);
}

(再次强调,每种类型都需要允许显式转换时,就需要添加1)

1
运算符=不能被重载。我想你是指运算符==。不要忘记运算符!=<=>= ;) - Martin Mulder
每个运算符都需要实现一次。如果您从该类型到MoneyAmount有一个隐式运算符,则不需要为每种类型都使用+运算符。在调用运算符方法之前,C#将把另一种类型转换为MoneyAmount。 - Martin Mulder
你看了我的最后一条评论吗?“那大约有30个运算符重载”是基于错误的假设。 - Martin Mulder
@MartinMulder 我一直在思考这个问题... :) int->double 没有隐式转换。所以如果你有 int n=1,并且你想要把它加到 MoneyAmount 上,但只有一个 double 的重载,那么你需要这样做:MoneyAmount m = 1.23; m+= (double)n; 我觉得这很奇怪。因此我会同时重载 double int - 我有漏掉了什么吗? - Jamiec
我编辑了我的答案以反映您的问题。从intdouble存在隐式转换。代码“double d = 2;”将编译。 - Martin Mulder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接