你是否考虑过这个问题:为什么不同类型之间的变量可以赋值,而不需要强制转换类型?如:  

    int i = 1;
long l = i;
object obj = 1;
Exception exception = new ArgumentNullException();
Array array = new string[0];
IEnumerable<int> enumerable = new List<int>();

  其实这是由C#中的隐式转换去完成的。目前,C#中可用的隐式转换有下面这些:

  1、标识转换

  标识转换表示任何类型可以在同一类型之间任意转换,这种转换看起来是理所当然的。

  其实,这种转换的由来,是由于C#4.0开始引入了dynamic,在运行时,dynamic和object可以认为是一样的,但是在编译时,编译器认为它们是不同类型,所以就需要一种转换来解决这个问题,这就是标识转换。

  2、隐式数值类型转换

  下面的数值之间可以隐式的转换,主要有:  

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

  3、隐式枚举类型转换

  隐式枚举转换表示数值 0 可以自动转换为任何的枚举类型,如:

    public enum DataType { String = 1, Number = 2 }//没有定义值为0的属性
static void Main(string[] args)
{
DataType type = 0;
Console.WriteLine(type);//输出0,因为枚举类型中没有一项的值为0
}

  4、隐式内插字符串转换

  隐式内插字符串转换表示内插字符串可以自动转换为 System.IFormattable 或者 System.FormattableString 类型的对象,如:  

    FormattableString formattableString = $"Number:{123}";
IFormattable formattable = $"DayOfWeek:{DayOfWeek.Monday}";
string value = $"String:{formattable}";

  5、隐式可空类型转换

  首先,我们知道,int、bool等和struct结构体类型是不可以为空的,也就是不能将 null 值赋值给它们,但是C#允许我们去创建它们的可空类型(在类型后加问号?就可以了),如:int?、bool?等(等价于 Nullable<int> 和 Nullable<bool>)。

  隐式可空类型转换就是指一个值类型可以隐式的转换为它的可空类型,如:    

    int i = 1;
int? nullable_i = i; bool b = true;
Nullable<bool> nullable_b = b; Guid guid = Guid.NewGuid();
Guid? nullable_guid = guid;

  6、null 隐式转换

  null 值可以转换为任何可以为 null 类型,如:  

    int? i = null;
Exception exception = null;

  7、隐式引用转换

  隐式引用转换表示的是引用类型之间的转换,包括:  

  • 任何引用类型可以隐式转换为 object 和 dynamic

  • 任何一个class类类型可以隐式转换为它的基类类型(包括基类的基类等)

  • 任何一个class类类型可以隐式转换为它所实现的接口类型

  • 任何一个接口类型可以隐式转换为它的所有父接口(包括父接口的父接口等)

  • 任意两个数组,比如 array1(元素类型是 T1)和 array2(元素类型是 T2), 如果他们满足下面三个条件,那么数组 array1 可以隐式的转换为数组 array2 :

    1、两个数组array1和array2有相同的维度
    2、T1 和 T2 都是引用类型
    3、T1 可以通过隐式引用转换成 T2

    比如:

    Exception[] exceptions = new ArgumentException[0][0];//不能转换,维度不同
    long[] longs = new int[0];//不能转换,int、long不是引用类型
    int? ints = new int[0];//不能转换,int不是引用类型
    string[] strs = new int[0];//不能转换,元素类型同 Exception[] array = new ApplicationException[0];//可以转换
  • 任何一个数组可以隐式的转换为 System.Array 类型及它所实现的接口类型(ICollection, IEnumerable, IList, IStructuralComparable, IStructuralEquatable, ICloneable)

  • 任意一个一维数组 T[] 可以隐式的转换为 System.Collections.Generic.IList<S> 及其所实现的接口类型( ICollection<S>, IEnumerable<S>, IEnumerable),但是要求 T、S 为引用类型,且 T 可以通过隐式引用类型转换成 S ,如:

    IList<long> list = new int[0];//不能转换,int、long不是引用类型
    IList<Exception> exceptions = new ArgumentException[0];//可以转换
  • 任何一个委托类型都可以隐式的转换为 System.Delegate 和它所实现的接口(ICloneable, ISerializable),如:

    Action action = () => Console.WriteLine("action");
    Delegate delegate1 = action; Func<bool> func = () => true;
    Delegate delegate2 = func;
  • null值可以隐式转换为任何引用类型

  • 如果一个类型 T1 可以通过标识转换或者隐式引用转换为 T2,而 T2 可以通过标识转换转换为 T3,那么 T1 也可以隐式转换为 T3 (这也是理所当然的)

  • 如果一个类型 T1 可以通过标识转换或者隐式易用转换成 T2,而 T2 可以通过Variance规则转换成 T3 ,那么 T1 也可以隐式转换为 T3

    其实这一点是针对Variance的,Variance是差异性转换,通常指的是协变、逆变、不变

    在定义泛型接口类型和泛型委托类型的时候,我们可以对泛型参数指定 in 或者 out关键字(只针对接口和委托有效),也可以不指定:

    协变:通过out关键字修饰的参数属于协变参数,在隐式转换时,泛型参数可以是这个泛型参数类型、它的子类、子接口等派生类,常见的比如IEnumerable<out T>、IEnumerator<out T>、IQueryable<out T>
    逆变:通过in关键字修饰的参数是逆变参数,与协变相反,在隐式转换时,泛型参数可以是这个泛型参数类型、它的父类型、父接口等,常见的如:Action<in T>,Func<in T1, out T2>
    不变:不使用out和in关键字,表示只能是同一个类型才能转换,如:List<T>、IList<>、Dictionary<TKey, TValue>、IDictionary<TKey, TValue>

    举个例子来对比理解,我们有以下几个类和接口:

    interface IGrandpa { }
    interface IFather : IGrandpa { }
    interface ISon : IFather { }
    class Grandpa : IGrandpa { }
    class Father : Grandpa, IFather { }
    class Son : Father, ISon { }

    对于协变:

    //协变,out参数指定的泛型可以使类型本身、子类型、子接口等
    IEnumerable<IFather> fathers1 = new IFather[0];//泛型参数本身
    IEnumerable<IFather> fathers2 = new List<Father>();//泛型参数是实现了接口的类
    IEnumerable<IFather> fathers3 = new List<ISon>();//泛型参数是子接口
    IEnumerable<IFather> fathers4 = new Son[0];//泛型参数是实现了子接口的类
    //错误用法
    IEnumerable<IFather> fathers5 = new IGrandpa[0];//报错,泛型参数不能是父接口
    IEnumerable<IFather> fathers6 = new Grandpa[0];//报错,泛型参数不能是父接口的实现类

    对于逆变:

    //逆变,in参数指定的泛型可以使类型本身、父类型、父接口等
    Action<IFather> action1 = f => Console.WriteLine(nameof(IFather));
    Action<IGrandpa> action2 = f => Console.WriteLine(nameof(IGrandpa));
    Action<Grandpa> action3 = f => Console.WriteLine(nameof(Grandpa));
    Action<Father> action4 = f => Console.WriteLine(nameof(Father)); Action<Father> father1 = action1;//泛型参数可以使实现的接口
    Action<Father> father2 = action2;//泛型参数可以是父接口
    Action<Father> father3 = action3;//泛型参数可以使父类
    Action<Father> father4 = action4;//泛型参数可以使本身 //错误用法
    Action<ISon> action5 = f => Console.WriteLine(nameof(ISon));
    Action<Son> action6 = f => Console.WriteLine(nameof(Son)); Action<Father> father5 = action5;//报错,泛型参数不能是子接口
    Action<Father> father6 = action6;//报错,泛型参数不能是子类 //Fun<in T1, out T2>是逆变和协变的结合
    Func<Father, IFather> func1 = f => new Father();
    Func<IFather, Father> func2 = f => new Father();
    Func<IGrandpa, ISon> func3 = f => new Son();
    Func<IFather, Son> func4 = f => new Son();
    Func<IGrandpa, Son> func5 = f => new Son(); Func<Father, IFather> ff1 = func1;//两个泛型参数都是自身
    Func<Father, IFather> ff2 = func2;//输入参数是逆变,输出是协变
    Func<Father, IFather> ff3 = func3;//输入参数是逆变,输出是协变
    Func<Father, IFather> ff4 = func4;//输入参数是逆变,输出是协变
    Func<Father, IFather> ff5 = func5;//输入参数是逆变,输出是协变

    对于不变:

    //不变,泛型参数必须一致
    IList<IFather> father1 = new List<IFather>();
    IList<Father> father2 = new List<Father>(); //错误用法
    IList<IFather> father3 = new List<Father>();
    IList<IFather> father4 = new List<Son>();
    IList<IFather> father5 = new List<ISon>();
    IList<IFather> father6 = new List<IGrandpa>();
    IList<IFather> father7 = new List<Grandpa>();

  8、装箱转换

  装箱转换主要指的是从值类型转换为引用类型,包括:

  • 任何值类型转换为object

  • 任何值类型转换为System.ValueType类(所有的struct类均派生自System.ValueType)

  • 任何非空的值类型转换为它们所实现的接口

  • 任何可空的值类型转换为它非空值类型所实现的接口

  • 任何的枚举类型转换为System.Enum

  • 任何可空的枚举类型转换为System.Enum

  举个例子:  

    //任何值类型转换为object
object obj1 = 1;//int装箱为object
object obj2 = false;//bool装箱为object

//任何值类型转换为System.ValueType类(所有的struct类均派生自System.ValueType)
ValueType valueType1 = 1;//int装箱为ValueType
ValueType valueType2 = false;//bool装箱为ValueType

IFormattable formattable = 1;//任何非空的值类型转换为它们所实现的接口
int? nullable_i = 1;
IComparable comparable = nullable_i;//任何可空的值类型转换为它非空值类型所实现的接口 Enum enum1 = DayOfWeek.Monday;//任何的枚举类型转换为System.Enum
DayOfWeek? dayOfWeek = DayOfWeek.Monday;
Enum enum2 = dayOfWeek;//任何可空的枚举类型转换为System.Enum //注意,对于可空值类型装箱时:
//1、如果可空值类型为null,那么结果是一个空指针引用(null)
//2、否则是装箱后的引用对象

  9、隐式动态转换

  隐式动态转换指的是dynamic类型转换为其他类型,这是一种运行是的转换,如果转换失败,将会抛出异常,如:  

    object @object = "hello";
dynamic @dynamic = "hello"; string value1 = @object;//编译时就报错
int value2 = @dynamic;//编译时不报错,运行时报错
string value3 = @dynamic;//编译和运行均通过

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

    dynamic @dynamic = new ArgumentException[0];
IList<Exception> exceptions = @dynamic;//可以转换

  10、隐式常量表达式转换

  常量表达式是在编译时计算的表达式,而且非在运行时,例如:  

    var sum = 1 + 2 + 3;
Console.WriteLine("sum is " + sum); var concat = "hello" + " " + "world";
Console.WriteLine(concat);

  在编译后,是这样子的:  

    int num = 6;
Console.WriteLine("sum is " + ((int)num).ToString());

string str = "hello world";
Console.WriteLine(str);

  可以看到,1+2+3 和 "hello" + " " + "world" 在编译时就被计算,这就是常量表达式。

  隐式常量表达式转换指的是下面两种情况下的转换:  

    1、值类型是int的常量表达式可以隐式的转换为sbyte, byte, short, ushort, uint, ulong,但是要求常量表达式的值在类型允许的范围内
2、值类型是long的常量表达式可以隐式的转换为ulong,但是要求常量表达式的值不能为负数

  例如:   

    byte b = 1 + 1;
short s = 2;
uint u = 100 * 2;
ulong l = 1314L * 520L;

  咋一看,这没什么,但是要知道,数值类型默认是int类型,也就是说,上面的byte类型的变量b的值来自于int类型!这就是隐式常量表达式转换的作用。

  需要注意的是,这里是常量表达式,而非变量值,且常量表达式的值应该在有效的范围内,否则将会抛出异常,如:  

    int i = 1;
short s = i;//报错,i不是常量表达式
byte b = 1314 + 520;//报错,byte类型值范围在0-255之间
ulong l = 99L - 100L;//报错,ulong类型不能为负数

  11、涉及类型参数的隐式转换

  这一点其实没什么特别的,一般指的就是泛型参数的转换,其实就是上面隐式转换作用在泛型类型上,参考下面的例子:

    public class Demo
{
//T是struct,所以T可以隐式的转换成ValueType
public ValueType GetValueType<T>(T t) where T : struct
{
return t;
}
//Exception实现了ISerializable接口,所以T可以隐式的转换成ISerializable
public ISerializable GetException<T>(T t) where T: Exception
{
return t;
}
}

  12、用户定义的隐式转换

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

  13、匿名函数转换和方法组转换

  匿名函数分为两种:Lambda表达式和匿名方法表达式

  Lambda表达式应该都很熟悉,它采用箭头符号来声明主体,而匿名方法表达式采用delegate关键字什么,与普通方法的区别就是没有名称,如:  

    //Lambda表达式
x => x + 1
x => { return x + 1; }
(int x) => x + 1
(int x) => { return x + 1; }
(x, y) => x * y
() => Console.WriteLine()
async (t1,t2) => await t1 + await t2 //匿名方法表达式
delegate (int x) { return x + 1; }
delegate { return 1 + 1; }

  匿名函数转换就是指Lambda表达式和匿名方法表达式可以隐式的转换成对应的委托,如:  

    //Lambda表达式转换为委托
Action action1 = () => Console.WriteLine();
Func<int, int> func1 = x => x + 1; //匿名方法表达式转换为委托
Action<int> action2 = delegate (int x) { Console.WriteLine(x); };
//匿名方法表达式中若未指定参数,则表示参数不匹配,可随意
Func<int> func2 = delegate { return 1 + 1; };
Func<int, int> func3 = delegate { return 1 + 1; };
Func<int, int, int> func4 = delegate { return 1 + 1; };

  方法组转换指的是,我们可以讲方法隐式的转换成委托,如:  

    public class Calculator
{
public int Plus(int a, int b)
{
return a + b;
}
public int Minus(int a, int b)
{
return a - b;
}
}
static void Main(string[] args)
{
//实例方法
Func<int, int, int> plus = new Calculator().Plus;
Func<int, int, int> minus = new Calculator().Minus;
//静态方法
Action<string[]> main = Main; //或者 //实例方法
Func<int, int, int> plus = new Func<int, int, int>(new Calculator().Plus);
Func<int, int, int> minus = new Func<int, int, int>(new Calculator().Minus);
//静态方法
Action<string[]> main = new Action<string[]>(Main);
}

  

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

  

C#中的隐式转换的更多相关文章

  1. JavaScript中关于隐式转换的一些总结

    JavaScript运算符中的隐式转换规律:一.递增递减运算符(前置.后置)1.如果包含的是有效数字字符串或者是有效浮点数字符串,则会将字符串转换(Number())为数值,再进行加减操作,返回值的类 ...

  2. C++中的隐式转换和explicit

    隐式转换 c++中的数据类型转换分为隐式转换和显示转换: 显示转换即使用static_cast等方法进行转换,相关内容请参考 <C++数据类型转换>: 隐式转换则是编译器完成的,如,boo ...

  3. Scala 中的隐式转换和隐式参数

    隐式定义是指编译器为了修正类型错误而允许插入到程序中的定义. 举例: 正常情况下"120"/12显然会报错,因为 String 类并没有实现 / 这个方法,我们无法去决定 Stri ...

  4. 【校招面试 之 C/C++】第18题 C++ 中的隐式转换以及explicit关键字

    1.什么是隐式转换: 众所周知,C++的基本类型中并非完全的对立,部分数据类型之间是可以进行隐式转换的. 所谓隐式转换,是指不需要用户干预,编译器私下进行的类型转换行为.很多时候用户可能都不知道进行了 ...

  5. mysql中的隐式转换

    在mysql查询中,当查询条件左右两侧类型不匹配的时候会发生隐式转换,可能导致查询无法使用索引.下面分析两种隐式转换的情况 看表结构 phone为 int类型,name为 varchar EXPLAI ...

  6. Js 中那些 隐式转换

    曾经看到过这样一个代码:  (!(~+[])+{})[--[~+""][+[]]*[~+[]]+~~!+[]]+({}+[])[[~!+[]*~+[]]] = sb , 你敢相信, ...

  7. MySQL性能优化:MySQL中的隐式转换造成的索引失效

    数据库优化是一个任重而道远的任务,想要做优化必须深入理解数据库的各种特性.在开发过程中我们经常会遇到一些原因很简单但造成的后果却很严重的疑难杂症,这类问题往往还不容易定位,排查费时费力最后发现是一个很 ...

  8. js中的隐式转换

    js中的不同的数据类型之间的比较转换规则如下: 1. 对象和布尔值比较 对象和布尔值进行比较时,对象先转换为字符串,然后再转换为数字,布尔值直接转换为数字 [] == true; //false [] ...

  9. C++中的explicit关键字 - 抑制隐式转换(转)

    在C++程序中很少有人去使用 explicit 关键字,不可否认,在平时的实践中确实很少能用的上.再说C++的功能强大,往往一个问题可以利用好几种C++特性去解决.但稍微留心一下就会发现现有的MFC库 ...

随机推荐

  1. OkHttp3 使用

    导入 compile 'com.squareup.okhttp3:okhttp:3.3.0' GET请求 String url = "https://www.baidu.com/" ...

  2. Linux学习 - shell脚本执行

    一.shell概述 shell是一个命令行解释器,为用户提供一个向Linux内核发送请求以便运行程序的界面系统级程序,用户可以用shell来启动.挂起.停止甚至是编写一些程序 shell还是一个功能强 ...

  3. OC-私有方法,构造方法,类的本质及启动过程

    总结 标号 主题 内容 一 OC的私有方法 私有变量/私有方法 二 @property 概念/基本使用/寻找方法的过程/查找顺序 三 @synthesize @synthesize概念/基本使用/注意 ...

  4. JS中操作JSON总结

    JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,采用完全独立于语言的文本格式,是理想的数据交换格式.同时,JSON是 JavaScript 原生格式,这意 ...

  5. Win10 Chrome 在DPI缩放下导致界面放大问题 解决方案

    支持:54.0.2840.59 m (64-bit) 以下大多数版本,具体未测试.如有问题可以反馈一下. 方法1:为程序设置"高DPI设置时禁用显示缩放. 方法2:为程序添加启动参数: /h ...

  6. 【Matlab】求矩阵行和/列和

    行和 sum(a, 2) 列和 sum(a) 所有元素之和 sum(sum(a)) 某一列元素之和 sum(a(:,1)) %a矩阵的第一列之和 某一行元素之和 sum(a(1,:)) %a矩阵的第一 ...

  7. 关于导入Eclips Web项目报错的解决方案

    1.是一定要有耐心,耐心,耐心,重要的事情说三遍.针对问题一 一破解,一步一步来,不要放弃. 2.其实百度就好了他们有报错的各种问题及解决方案 ,包括导入项目web.xml报错,js文件,jsp文件报 ...

  8. 服务器安装Centos7

    目录 一.安装 一.安装 1.开启虚拟机后会出现以下界面 Install CentOS 7 安装CentOS 7 Test this media & install CentOS 7 测试安装 ...

  9. ubuntu 16.04下的fastadmin安装指南

    此篇博客转载于fastadmin论坛,方便自己看转到了博客里 说明文档不多,特制作一个,方便大家交流使用Ubuntu 16.04 安装fastadmin指南本文因考虑到大多数人员,习惯性在window ...

  10. RabbitMQ,RocketMQ,Kafka 消息模型对比分析

    消息模型 消息队列的演进 消息队列模型 发布订阅模型 RabbitMQ的消息模型 交换器的类型 direct topic fanout headers Kafka的消息模型 RocketMQ的消息模型 ...