大多数编程语言都支持显示转换,也称为强制转换,它与隐式转换相呼应,比如,一般的,整型可以通过隐式转换成浮点型,而浮点型需要通过强制转换成整型:  

int i = 32;
    double d = i;//整型隐式转换成浮点型
    int j = (int)d;//浮点型显示转换成整型

  目前,C#中可用的隐式转换有下面这些:

  1、所有隐式转换

  隐式转换时自动完成的,那隐式转换也支持强制转换,这无可厚非。

  2、显式数值类型转换

  显式数值转换包括:  

1、sbyte 类型可以显式转换为 byte, ushort, uint, ulong, char.
    2、byte 类型可以显式转换为 sbyte, char.
    3、short 类型可以显式转换为 sbyte, byte, ushort, uint, ulong, char.
    4、ushort 类型可以显式转换为 sbyte, byte, short, or char.
    5、int 类型可以显式转换为 sbyte, byte, short, ushort, uint, ulong, char.
    6、uint 类型可以显式转换为 sbyte, byte, short, ushort, int, char.
    7、long 类型可以显式转换为 sbyte, byte, short, ushort, int, uint, ulong, char.
    8、ulong 类型可以显式转换为 sbyte, byte, short, ushort, int, uint, long, char.
    9、char 类型可以显式转换为 sbyte, byte, or short.
    10、float 类型可以显式转换为 sbyte, byte, short, ushort, int, uint, long, ulong, char, decimal.
    11、double 类型可以显式转换为 sbyte, byte, short, ushort, int, uint, long, ulong, char, float, decimal.
    12、decimal 类型可以显式转换为 sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double.

  需要注意的是,数值类型在转换时,可能会丢失精度,或者抛出异常,其中转换的规则按照下面的方式进行:

  • 整型数据强制转换为另一个整型数据,它的转换依赖于是否使用了checked和unchecked上下文检测:
如:整型(S)显式转换为整型(T)
    
    1、使用checked上下文检测时,当S数据在T所允许的范围内时,转换可以成功进行,否则抛出System.OverflowException
    2、使用unchecked上下文检测时:
        当S的位数大于T的位数时,转换时会截取到多余的高位(二进制),剩下的位数作为T数据输出
        当S的位数小于T的位数时,转换时将对S数据(二进制)进行拓展(无符号整数使用0拓展,有符号整数,如果小于0使用1拓展,否则使用0拓展),将拓展后的值作为T数据输出
        当S的位数与T的位数相等时,则不需要截取也不需要拓展,S数据直接转换成T数据
    3、未使用checked和unchecked上下文检测时,则根据编译时选择的默认的上下文检测方式(一般是unchecked)

例如:

//--------------------------------------checked上下文检测--------------------------------------------------
    
    long l1 = 100000L;
    int value1 = checked((int)l1);      //正常转换
    int value2 = checked((int)(l1 * l1));//报错OverflowException
    
    //--------------------------------------unchecked上下文检测--------------------------------------------------
    
    long l2 = 100000L * 100000L;//10000000000
    int value3 = unchecked((int)l2);//1410065408
    //long是64位>int的32位,l2的二进制:01001010100000010111110010000000000
    //截取掉多余的3个高位,得到剩下的32位的二进制:01010100000010111110010000000000,即1410065408
    
    sbyte b = -1;//有符号8位
    uint value4 = unchecked((uint)b);//4294967295
    //sbyte是8位<uint的32位,b的二进制:1111111111111111
    //因为sbyte是有符号整数且b<0,所以使用1拓展高位得到32位的二进制:11111111111111111111111111111111,即4294967295    
    
    uint ui = 3147483647;
    int value5 = unchecked((int)ui);//-1147483649
    //uint是32位=int的32位,ui的二进制:10111011100110101100100111111111
    //所以直接就是int的二进制:10111011100110101100100111111111,即-1147483649
  •  decimal浮点数强制转换成整型数据时,会向零取整舍弃小数部分,如果取整后的值不在转换类型所允许的范围内,将会抛出System.OverflowException,例如:
decimal d1 = 1.6m;
    decimal d2 = -1.6m;
    decimal d3 = 314m;
    sbyte b1 = (sbyte)d1;//1
    sbyte b2 = (sbyte)d2;//-1
    sbyte b3 = (sbyte)d3;//报错System.OverflowException
  • float和double强制转换成整型数据时,依赖于是否使用了checked和unchecked上下文检测:
1、使用checked上下文检测时:
        如果float或者double值是NaN或者无穷值(NegativeInfinity和PositiveInfinity),则会抛出System.OverflowException
        否则将float或者double值向零取整舍弃小数部分,如果取整后的值不在整数类型允许的范围内,则会抛出System.OverflowException
    2、使用unchecked上下文检测时:
        如果float或者double值是NaN或者无穷值(NegativeInfinity和PositiveInfinity),转换会取消,返回整型的默认值
        否则将float或者double值向零取整舍弃小数部分,如果取整后的值不在整数类型允许的范围内,转换会取消,返回整型的默认值(这一点官方文档说的不准确,看下面的例子)
    3、未使用checked和unchecked上下文检测时,则根据编译时选择的默认的上下文检测方式(一般是unchecked)

例如:

double d1 = double.NaN;
    double d2 = 1.6d;
    double d3 = -1.6d;
    double d4 = 3147483647.25d;
    double d5 = 314.25d;
    
    //--------------------------checked上下文检测----------------------------------
    
    sbyte b1 = checked((sbyte)d1);//报错System.OverflowException
    sbyte b2 = checked((sbyte)d2);//1
    sbyte b3 = checked((sbyte)d3);//-1
    sbyte b4 = checked((sbyte)d4);//报错System.OverflowException
    
    //--------------------------unchecked上下文检测--------------------------------
    
    sbyte b5 = unchecked((sbyte)double.PositiveInfinity);   //0
    sbyte b6 = unchecked((sbyte)2.5d);                      //2
    sbyte b7 = unchecked((sbyte)-3.5d);                     //-3
    sbyte b8 = unchecked((sbyte)314.25d);                   //0
    byte b9 = unchecked((byte)257.25d);                     //0
    short b10 = unchecked((short)3147483647.25d);           //0
    int b11 = unchecked((int)3147483647.25d);               //0
    
    //下面的和官方文档的是不一样(有空再找找原因,可能是编译时和运行时的设置问题)
    int b12 = unchecked((int)d4);                           //-2147483648
    sbyte b13 = unchecked((sbyte)d5);                       //58
  • double强制转换成float时,遵从规则:
1、一般情况下,double值会通过向最接近的float值进行取舍(非四舍五入),保证精度丢失比较小
    2、如果一个double值无限趋向于0,当精度超过float所允许的精度时(float.Epsilon),会将+0或者-0作为结果返回,这取决于double是正数还是负数
    3、如果一个double值非常大,大于float所允许的范围时,会将无穷大返回(float.NegativeInfinity或者float.PositiveInfinity,这取决于double是正数还是负数)

例如:

double d0 = 3.1415d;
    double d1 = 3.141592653589793d;
    double d2 = double.NaN;
    double d3 = 3.14E-100d;
    double d4 = -3.14E-100d;
    double d5 = double.MaxValue;
    double d6 = double.MinValue;
    
    float f0 = (float)d0;//3.1415
    float f1 = (float)d1;//3.1415927
    float f2 = (float)d2;//NaN
    float f3 = (float)d3;//+0
    float f4 = (float)d4;//-0
    float f5 = (float)d5;//float.PositiveInfinity
    float f6 = (float)d6;//float.NegativeInfinity
  •  double、float强制转换成decimal时,遵从规则:
1、一般的,double和float值会直接转换成decimal,当小数据超过28位时,如果需要会自动进行取舍
    2、如果double和float值太小而趋向于0,则会直接返回0
    3、如果double和float值是NaN、无穷大、或者是超过decimal运行的范围,那么将会抛出System.OverflowException

例如:

double d0 = 3.1415d;
    double d1 = 3.14159265358979323d;
    double d2 = double.NaN;
    double d3 = 3.14E-200d;
    double d4 = -3.14E-200d;
    double d5 = double.MaxValue;
    double d6 = double.MinValue;
    
    decimal value0 = (decimal)d0;//3.1415
    decimal value1 = (decimal)d1;//3.14159265358979
    decimal value2 = (decimal)d2;//抛出System.OverflowException
    decimal value3 = (decimal)d3;//0
    decimal value4 = (decimal)d4;//0
    decimal value5 = (decimal)d5;//抛出System.OverflowException
    decimal value6 = (decimal)d6;//抛出System.OverflowException
  •  decimal强制转换成double或者float时,都可以转换成功,不会抛出任何异常,但是可能会造成精度的丢失,例如:
decimal d0 = 3.1415m;
    decimal d1 = 3.14159265358979323m;
    
    float value0 = (float)d0;//3.1415
    float value1 = (float)d1;//3.1415927

  3、显式枚举类型转换

  显式枚举类型转换包括:  

1、sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal 可以显式转换为任何枚举类型.
    2、任何枚举类型可以显式的转换为 sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal.
    3、任意两个枚举类型之间可以进行显式转换.

  其实,枚举类型底层还是整型数据,也就是说枚举类型的显示转换本质上还是整型数据的强制转换问题。

  4、显式可空类型转换

  显式可空类型转换指的是,如果一个值类型S可以显式的转换成另一个值类型T,那么S和T还存在下列显式的转换:  

1、S? 可通过显式转换成 T?,如果S?的值是null,那么转换后的T?也是null,否则等价于 S?->S->T->T? 的过程
    2、S 可通过显式转换成 T?,这等价于 S->T->T? 的过程
    3、S? 可通过显式转换成 T,如果S?的值是null,则会抛出System.InvalidOperationException,否则等价于 S->T 的过程

  5、显式引用转换

  显式引用转换包括:  

1、object 和 dynamic 可以显式引用转换成任何其他的引用类型
    2、基类(父类)可以通过显式引用转换成一个子类
    3、任何一个非sealed的类可以通过显式引用转换成任何一个接口,不管这个类是否实现了这个接口
    4、任何一个接口可以通过显式引用转换成任何一个非sealed的类,不管这个类是否实现了这个接口
    5、任何一个接口可以通过显式引用转换成任何一个接口,不管它们是否是父子接口关系
    6、如果任意两个引用类型之间存在一个显式引用转换,那么它们相同维度的数组之间也可以通过显式引用转换
    7、System.Array即它所实现的接口(如System.Collections.IList),可以显式引用转换为任意的数组类型
    8、如果一个类型S到另一个类型T存在显式引用转换,那么S的一维数组S[]到System.Collections.Generic.IList<T>及它的父接口(如System.Collections.Generic.ICollection<T>)也存在显式引用转换
    9、如果一个类型S到另一个类型T存在显式引用转换,那么System.Collections.Generic.IList<S>及它的父接口(如System.Collections.Generic.ICollection<S>)到T的一维数组T[]也存在显式引用转换
    10、System.Delegate及它所实现的所有接口可以显式引用转换成任意的委托类型
    11、如果一个引用类型T1存在显式引用转换到引用类型T2,T2存在标识转换到T3,那么T1也可以通过显式引用转换成T3
    12、如果一个引用类型T1存在显式引用转换到一个接口或者委托类型T2,而T2与T3之间存在协变、逆变或者不变,那么T1也可以通过显式引用转换成T3
    14、如果D<X1,X2,...,Xn>是一个泛型委托类型,当Xi满足下面条件之一时,那么D<S1,S2...Sn>到D<T1,T2...Tn>存在显式引用转换:
        Xi是不变,且Si到Ti相同
        Xi是协变,且Si到Ti存在显式和隐式的标识或引用转换
        Xi是逆变,且Si和Ti要么相同,要么都是引用类型

  简单的例子:  

public class Parent { }
    public class Child : Parent { }
    public interface IPerson { }
    public interface IAnimal { }
    
    object obj = null;
    Parent parent = (Parent)obj;            //object 和 dynamic 可以显式引用转换成任何其他的引用类型
    Child child = (Child)parent;            //基类(父类)可以通过显式引用转换成一个子类
    IPerson person = (IPerson)child;        //任何一个非sealed的类可以通过显式引用转换成任何一个接口,不管这个类是否实现了这个接口
    Exception exception = (Exception)person;//任何一个接口可以通过显式引用转换成任何一个非sealed的类,不管这个类是否实现了这个接口
    IAnimal animal = (IAnimal)person;       //任何一个接口可以通过显式引用转换成任何一个接口,不管它们是否是父子接口关系
    
    //如果任意两个引用类型之间存在一个显式引用转换,那么它们相同维度的数组之间也可以通过显式引用转换
    IAnimal[] animals = (IAnimal[])new IPerson[0];
    IPerson[,] persons = (IPerson[,])new IAnimal[1, 2];
    
    //System.Array即它所实现的接口(如System.Collections.IList),可以显式引用转换为任意的数组类型
    Array array = null;
    IList list = null;
    int[] ints = (int[])array;
    double[,] doubles = (double[,])list;
    
    //如果一个类型S到另一个类型T存在显式引用转换,那么S的一维数组S[]到System.Collections.Generic.IList<T>及它的父接口(如System.Collections.Generic.ICollection<T>)也存在显式引用转换
    Parent[] parents = null;
    IList<Child> children = (IList<Child>)parents;
    ICollection<Child> children1 = (ICollection<Child>)parents;
    //反之亦然:如果一个类型S到另一个类型T存在显式引用转换,那么System.Collections.Generic.IList<S>及它的父接口(如System.Collections.Generic.ICollection<S>)到T的一维数组T[]也存在显式引用转换
    parents = (Parent[])children;
    parents = (Parent[])children1;
    
    //System.Delegate及它所实现的所有接口可以显式引用转换成任意的委托类型
    Delegate @delegate = null;
    ICloneable cloneable = null;
    Func<int> func = (Func<int>)@delegate;
    Action action = (Action)cloneable;
    
    //如果一个引用类型T1存在显式引用转换到一个接口或者委托类型T2,而T2与T3之间存在协变、逆变或者不变,那么T1也可以通过显式引用转换成T3
    Array array1 = new Child[0];
    IEnumerable<Parent> parents1 = (IEnumerable<Parent>)array1;//System.Array可以显式引用转换成Child[],而Child[]到IEnumerable<Parent>存在协变
    
    //如果D<X1,X2,...,Xn>是一个泛型委托类型,当Xi满足下面条件之一时,那么D<S1,S2...Sn>到D<T1,T2...Tn>存在显式引用转换:
    //Xi是不变,且Si到Ti相同
    //Xi是协变,且Si到Ti存在显式和隐式的标识或引用转换
    //Xi是逆变,且Si和Ti要么相同,要么两种引用类型
    Func<Parent, Child> func1 = p => (Child)p;
    Func<Child, Parent> func2 = (Func<Child, Parent>)func1;

注:因为显式引用转换是在运行时检查的,所以我们尽量保证待转换的值是null或者是可转换的,否则将会抛出System.InvalidCastException

  6、拆箱转换

  装箱转换允许将一个值类型包装成引用类型来使用,而拆箱转换则相反,将引用类型拆箱得到里面的值类型,目前拆箱转换包括:  

1、object 可以拆箱转换成值类型
    2、System.ValueType 可以拆箱转换成任何值类型.
    3、任何接口可以拆箱转换成实现了这个接口的非空值类型
    4、任何接口可以拆箱转换成实现了这个接口的值类型的可空类型
    5、System.Enum可以拆箱转换成任何枚举类型
    6、System.Enum可以拆箱转换成任何枚举类型的可空类型

  例如

object obj = 1;
    ValueType value = 2L;
    int i = (int)obj;          //object 可以拆箱转换成值类型
    long l = (long)value;      //System.ValueType 可以拆箱转换成任何值类型
    
    IFormattable formattable = 3.14f;
    float s = (float)formattable;           //任何接口可以拆箱转换成实现了这个接口的非空值类型
    float? nullable_s = (float?)formattable;//任何接口可以拆箱转换成实现了这个接口的值类型的可空类型
    
    Enum @enum = DayOfWeek.Monday;
    DayOfWeek dayOfWeek1 = (DayOfWeek)@enum;    //System.Enum可以拆箱转换成任何枚举类型
    DayOfWeek? dayOfWeek2 = (DayOfWeek?)@enum;  //System.Enum可以拆箱转换成任何枚举类型的可空类型

注:因为拆箱转换是在运行时检查的,说我们要尽量保证它是可拆箱的,如果将一个null值拆箱给一个非空的值对象,那么将会抛出System.NullReferenceException,如果拆箱的值不是合适的类型,那么将会抛出System.InvalidCastException。

  7、显式动态转换

  显式动态转换指的是dynamic可以显式转换成任意的其他类型,需要注意的是,它是运行时检查转换的,如果转换失败,将会抛出异常。

dynamic @dynamic = 3.14f;
    long l = (long)@dynamic;//可以显式动态转换
    int i = @dynamic;//运行时将报错,float不能通过隐式转换成int

  一般的,如果dynamic对象保存的类型不是所需要的类型,那么它会先将dynamic转换陈给对象,再由对象转换成所需要的类型,也就是说,如果类型S可以通过显式转换成T,那么S也可以通过显式动态转换成T。

  8、涉及类型参数的显式转换

  这里指的一般是泛型参数的转换,其实显式转换作用在泛型类型上,参考下面的例子:  

public class Parent
    {
        public static explicit operator string(Parent parent) => parent?.ToString();
    }
    public class Demo
    {
        public int E<T>(T t)
        {
            return (int)t;//报错,因为不知道泛型T是什么类型
        }
        public int F<T>(T t)
        {
            return (int)(object)t;//这样写可以不报错
        }
        public string G<T>(T t) where T : Parent
        {
            return (string)t;//可以转换,Parent类具有自定义转换到string
        }
    
    }

  9、用户自定义的显式转换

  C#允许用户可自定义类型或者结构的自定义转换,可以看看:C#自定义转换(implicit 或 explicit)

 

  参考文档:https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/conversions#explicit-conversions

 

一个专注于.NetCore的技术小白