理解:
- 使用等号代替构造函数调用的效果
- 以类似重载操作符的形式定义用于类型转换的函数
- 前者类型转换时候直接写等号赋值语法,后者要额外加目标类型的强制转换
- stirng str -> object o -> int a 可以 int a = (int)(str as object)转换通过编译,但没有转换逻辑所以运行会报错,explicit就可以解决这个问题,类似 于dart语言中的 源类型对象.to目标类型() 的意思.
什么是隐式转换?
int i = 1;
double d = i;
什么是显式转换?
double d = 1;
int i = (int)d
implicit
隐式转换,相当于封箱操作如: int i = 1; object obj = i;
如果不理解封箱可以换个例子: int i = 1; double d = i;
double的可取值范围包含了int的全部可取值范围,所以可以int->double隐式转换
explicit
显式转换,相当于拆箱操作如: object obj = 1; int i = (int)obj;
如果不理解封箱可以换个例子: double i = 1; int i = (int) d;
为什么有显式和隐式转换?
在C#本身的类型转换中,
int, uint , float之类的都可以隐式转换到double,是因为 double可以不丢失精度的情况下保存int, uint , float之类类型的值
而 double要想转换回到其他数值类型,则有可能丢失精度,所以我们要强制转换,也就是显式的指定转换的方式,即
double d = 1.1;
//int i = d;//这样不可以转换
int i = (double)d; //这样可以转换,但是会丢精度
此时 i 的值为1而不是1.1(向下取整,即Math.Floor()函数执行后再转为int的相同作用.
故,
方便,是隐式转换的主要目的
明确,精确,安全,是显式转换的主要目的
implicit/explicit的用法
固定以 public static implicit/explicit operator 目标类型(源类型 源类型形参)
的方式使用.
假设我们有人民币Rmb和美元Dollar类
评估资产的时候,默认都用Dollar来作为通用单位,当需要换成Rmb或者其他比重比如Krw的时候,需要显式的,明确的知道要转换的目标币种的类型.
比如隐式转换 Rmb->Dollar
public static implicit operator Dollar(Rmb rmb)
{
//汇率相关的计算
}
再比如显式转换 Dollar->Rmb
public static explicit operator Rmb(Dollar rmb)
{
//汇率先关的计算
}
使用时,隐式转换直接用Rmb可以直接给Dollar赋值
var rmb = new Rmb();
var dollar = rmb;//可以正常编译通过并在运行时正确转换
而
var doaalr = new Dollar();
//var rmb = dollar;//不可以通过编译,因为没有实现从dollar到rmb的隐式转换
var rmb = (Rmb)dollar;//可以通过编译并在运行时正确转换.
设计建议:
不管隐式还是显式转换都要保证类型安全不溢出不抛错
隐式转换设计时尽量源类型和目标类型不要有太多的偏差,否则容易造成歧义,如
var person = new Person(){ Id = 1111 };
int id = person; //这种虽然是方便从person中取出Id属性赋值给id,但理解可能会有偏差.
尽管我们可以用
int personId = person; 仍然是有较大的歧义.读代码的人会想id怎么会是一个"人"对象呢?
所以都不如 var id = person.Id来的直观.
像public class ArgsInfo内定义一个隐式转换从 string [] 到 ArgsInfo就是一个较好的设计
//一段精简的示例代码,实际设计会比这个健壮,仅为了表示该类和 string[] args较好耦合.
public class ArgsInfo
{
private readonly Dictionary<string,string> _args = new();
public static implicit operator ArgsInfo(string[] args)
{
var result = new ArgsInfo();
foreach (var arg in args)
{
var kv = arg.Split('=');
if (kv.Length == 2)
{
result._args[kv[0]] = kv[1];
}
}
return result;
}
public override string ToString()
{
var sb = new StringBuilder();
foreach (var (key, value) in _args)
{
sb.AppendLine($"{key}={value}");
}
return sb.ToString();
}
}
public static void Main(string[] args)
{
//就像调用了 var argsInfo = new ArgsInfo(args);
ArgsInfo argsInfo = args;
Console.WriteLine(argsInfo);
}
显示转换设计时, 语义会更明确, 但必要的类型转换说明不可少.丢不丢精度,等都要写清
由于不像函数ConvertXXXToYYY, src.ToDestType<int>(), src.toInt()等直观的通过名称就知道含义且可以传递各种参数如精度之类的,所以保证类型转换的安全稳定和易维护拓展很重要.
完整示例代码带注释:
新建一个cs文件,直接运行Test方法看看效果吧
Rider截图:
/*
implicit:隐式转换
explicit:显式转换
*/
using System.Globalization;
namespace CS2TS.Test._1_InTestCSFiles;
/// <summary>
/// 常量值定义
/// </summary>
public static class Constant
{
/// <summary>
/// 1美元换多少人民币
/// </summary>
public const double DollarToRmb = 6.5;
/// <summary>
/// 1美元换多少韩元
/// </summary>
public const double DollarToKrw = 1100;
}
/// <summary>
/// 人
/// </summary>
public class Person
{
/// <summary>
/// 名字
/// </summary>
public string Name { get; set; } = "无名氏";
/// <summary>
/// 资产,默认以可隐式转换的美元表示,转换成其他货币需要显式转换标明意图
/// </summary>
public Dollar Money { get; set; } = new();
}
public class Rmb
{
public double RmbAmount { get; set; }
public static implicit operator Dollar(Rmb rmb)
{
return new Dollar
{
DollarAmount = rmb.RmbAmount / Constant.DollarToRmb
};
}
public static implicit operator Rmb(Dollar dollar)
{
return new Rmb
{
RmbAmount = dollar.DollarAmount * Constant.DollarToRmb
};
}
public static explicit operator Krw(Rmb rmb)
{
//先把rmb转换成dollar,然后再转换成krw
return (Dollar)rmb;
}
public static explicit operator Rmb(Krw krw)
{
//先把krw转换成dollar,然后再转换成rmb
return (Dollar)krw;
}
}
/// <summary>
/// 韩元
/// </summary>
public class Krw
{
public double KrwAmount { get; set; }
/*
可以直接用美元换韩元
如果换成人民币则需要显式转换
*/
public static implicit operator Dollar(Krw krw)
{
return new Dollar
{
DollarAmount = krw.KrwAmount / Constant.DollarToKrw
};
}
public static implicit operator Krw(Dollar dollar)
{
return new Krw
{
KrwAmount = dollar.DollarAmount * Constant.DollarToKrw
};
}
public static explicit operator Rmb(Krw krw)
{
//先把krw转换成dollar,然后再转换成rmb
return (Dollar)krw;
}
public static explicit operator Krw(Rmb rmb)
{
//先把rmb转换成dollar,然后再转换成krw
return (Dollar)rmb;
}
}
/// <summary>
/// 美元,作为中间货币,人民币和韩元都可以直接换成美元,但是美元要换成什么,需要显式转换
/// </summary>
public class Dollar
{
public double DollarAmount { get; set; }
public override string ToString()
{
return DollarAmount.ToString(CultureInfo.InvariantCulture);
}
}
public class ImplicitAndExplicit
{
public static void Test()
{
#region 小明,美元换韩元和人民币
var ming = new Person
{
Name = "小明",
Money = new Dollar
{
DollarAmount = 1000
}
};
//他想换成韩元的或者人民币的时候,需要显示的转换
var rmbOfMing = (Rmb)ming.Money;
Console.WriteLine($"小明有{ming.Money.DollarAmount}美元,换成人民币是{rmbOfMing.RmbAmount}元");
var krwOfMing = (Krw)ming.Money;
Console.WriteLine($"小明有{ming.Money.DollarAmount}美元,换成韩元是{krwOfMing.KrwAmount}元");
#endregion
#region 小红,人民币换韩元
Console.WriteLine("小红只有人民币1000元,但是出国换货币的时候,都是央行的汇率,所以她的人民币要先转换成美元,然后再转换成其他货币");
var rmbOfHong = new Rmb
{
RmbAmount = 1000
};
var hong = new Person
{
Name = "小红",
// 不需要显示转换,因为Rmb有implicit转换成Dollar
Money = rmbOfHong
};
//需要显示转换,因为Dollar没有implicit转换成Rmb
var krwOfHong = (Krw)hong.Money;
Console.WriteLine($"小红有{hong.Money.DollarAmount}美元,换成韩元是{krwOfHong.KrwAmount}元");
#endregion
#region 小黑,韩元换人民币
Console.WriteLine("小黑只有韩元1000元,但是出国换货币的时候,都是央行的汇率,所以她的韩元要先转换成美元,然后再转换成其他货币");
var krwOfHei = new Krw
{
KrwAmount = 1000
};
var hei = new Person
{
Name = "小黑",
// 不需要显示转换,因为Krw有implicit转换成Dollar
Money = krwOfHei
};
//需要显示转换,因为Dollar没有implicit转换成Rmb
var rmbOfHei = (Rmb)hei.Money;
Console.WriteLine($"小黑有{hei.Money.DollarAmount}美元,换成人民币是{rmbOfHei.RmbAmount}元");
#endregion
#region 统一用Dollar来表示资产
Console.WriteLine($"小明有{ming.Money.DollarAmount}美元");
Console.WriteLine($"小红有{hong.Money.DollarAmount}美元");
Console.WriteLine($"小黑有{hei.Money.DollarAmount}美元");
#endregion
#region 统一用Rmb来表示资产
Console.WriteLine($"小明有{((Rmb)ming.Money).RmbAmount}人民币");
Console.WriteLine($"小红有{((Rmb)hong.Money).RmbAmount}人民币");
Console.WriteLine($"小黑有{((Rmb)hei.Money).RmbAmount}人民币");
#endregion
#region 统一用Krw来表示资产
Console.WriteLine($"小明有{((Krw)ming.Money).KrwAmount}韩元");
Console.WriteLine($"小红有{((Krw)hong.Money).KrwAmount}韩元");
Console.WriteLine($"小黑有{((Krw)hei.Money).KrwAmount}韩元");
#endregion
}
}