我认为这个问题是关于在 C# 应用程序中创建类型安全的更一般问题的具体案例。我这里的例子有两种类型的数据:价格和重量。它们有不同的计量单位,因此永远不要尝试为重量分配价格,反之亦然。两者都是真正的十进制值。 (我忽略了可能存在磅到公斤等转换的事实。)同样的想法可以应用于具有特定类型的字符串,例如 EmailAddress 和 UserLastName。
使用一些相当样板的代码,可以在特定类型之间进行显式转换或隐式转换:Price 和 Weight,以及底层类型 Decimal。
public class Weight
{
private readonly Decimal _value;
public Weight(Decimal value)
{
_value = value;
}
public static explicit operator Weight(Decimal value)
{
return new Weight(value);
}
public static explicit operator Decimal(Weight value)
{
return value._value;
}
};
public class Price {
private readonly Decimal _value;
public Price(Decimal value) {
_value = value;
}
public static explicit operator Price(Decimal value) {
return new Price(value);
}
public static explicit operator Decimal(Price value)
{
return value._value;
}
};
通过“显式”运算符覆盖,人们可以使用这些类获得一组更具限制性的事情。每次从一种类型更改为另一种类型时,您都必须手动设置大小写。例如:
public void NeedsPrice(Price aPrice)
{
}
public void NeedsWeight(Weight aWeight)
{
}
public void NeedsDecimal(Decimal aDecimal)
{
}
public void ExplicitTest()
{
Price aPrice = (Price)1.23m;
Decimal aDecimal = 3.4m;
Weight aWeight = (Weight)132.0m;
// ok
aPrice = (Price)aDecimal;
aDecimal = (Decimal)aPrice;
// Errors need explicit case
aPrice = aDecimal;
aDecimal = aPrice;
//ok
aWeight = (Weight)aDecimal;
aDecimal = (Decimal) aWeight;
// Errors need explicit cast
aWeight = aDecimal;
aDecimal = aWeight;
// Errors (no such conversion exists)
aPrice = (Price)aWeight;
aWeight = (Weight)aPrice;
// Ok, but why would you ever do this.
aPrice = (Price)(Decimal)aWeight;
aWeight = (Weight)(Decimal)aPrice;
NeedsPrice(aPrice); //ok
NeedsDecimal(aPrice); //error
NeedsWeight(aPrice); //error
NeedsPrice(aDecimal); //error
NeedsDecimal(aDecimal); //ok
NeedsWeight(aDecimal); //error
NeedsPrice(aWeight); //error
NeedsDecimal(aWeight); //error
NeedsWeight(aWeight); //ok
}
只需通过将代码中的“显式”替换为“隐式”将“显式”运算符更改为“隐式”运算符,就可以在没有任何额外工作的情况下来回转换到底层的 Decimal 类。这使得价格和重量的行为更像小数,但您仍然不能将价格更改为重量。这通常是我正在寻找的类型安全级别。
public void ImplicitTest()
{
Price aPrice = 1.23m;
Decimal aDecimal = 3.4m;
Weight aWeight = 132.0m;
// ok implicit cast
aPrice = aDecimal;
aDecimal = aPrice;
// ok implicit cast
aWeight = aDecimal;
aDecimal = aWeight;
// Errors
aPrice = aWeight;
aWeight = aPrice;
NeedsPrice(aPrice); //ok
NeedsDecimal(aPrice); //ok
NeedsWeight(aPrice); //error
NeedsPrice(aDecimal); //ok
NeedsDecimal(aDecimal); //ok
NeedsWeight(aDecimal); //ok
NeedsPrice(aWeight); //error
NeedsDecimal(aWeight); //ok
NeedsWeight(aWeight); //ok
}
当对 String 而不是 Decimal 执行此操作时。我喜欢 Thorarin 关于检查 null 并在转换中传回 null 的答案的想法。例如
public static implicit operator EMailAddress(string address)
{
// Make
// EmailAddress myvar=null
// and
// string aNullString = null;
// EmailAddress myvar = aNullString;
// give the same result.
if (address == null)
return null;
return new EMailAddress(address);
}
要让这些类作为 Dictionary 集合的键,您还需要实现 Equals、GetHashCode、operator == 和 operator !=
为了使这一切更容易,我创建了一个可以扩展的 ValueType 类,ValueType 类调用除转换运算符之外的所有内容的基类型。