C#中的隐式转换
你是否考虑过这个问题:为什么不同类型之间的变量可以赋值,而不需要强制转换类型?如:
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#中的隐式转换的更多相关文章
- JavaScript中关于隐式转换的一些总结
JavaScript运算符中的隐式转换规律:一.递增递减运算符(前置.后置)1.如果包含的是有效数字字符串或者是有效浮点数字符串,则会将字符串转换(Number())为数值,再进行加减操作,返回值的类 ...
- C++中的隐式转换和explicit
隐式转换 c++中的数据类型转换分为隐式转换和显示转换: 显示转换即使用static_cast等方法进行转换,相关内容请参考 <C++数据类型转换>: 隐式转换则是编译器完成的,如,boo ...
- Scala 中的隐式转换和隐式参数
隐式定义是指编译器为了修正类型错误而允许插入到程序中的定义. 举例: 正常情况下"120"/12显然会报错,因为 String 类并没有实现 / 这个方法,我们无法去决定 Stri ...
- 【校招面试 之 C/C++】第18题 C++ 中的隐式转换以及explicit关键字
1.什么是隐式转换: 众所周知,C++的基本类型中并非完全的对立,部分数据类型之间是可以进行隐式转换的. 所谓隐式转换,是指不需要用户干预,编译器私下进行的类型转换行为.很多时候用户可能都不知道进行了 ...
- mysql中的隐式转换
在mysql查询中,当查询条件左右两侧类型不匹配的时候会发生隐式转换,可能导致查询无法使用索引.下面分析两种隐式转换的情况 看表结构 phone为 int类型,name为 varchar EXPLAI ...
- Js 中那些 隐式转换
曾经看到过这样一个代码: (!(~+[])+{})[--[~+""][+[]]*[~+[]]+~~!+[]]+({}+[])[[~!+[]*~+[]]] = sb , 你敢相信, ...
- MySQL性能优化:MySQL中的隐式转换造成的索引失效
数据库优化是一个任重而道远的任务,想要做优化必须深入理解数据库的各种特性.在开发过程中我们经常会遇到一些原因很简单但造成的后果却很严重的疑难杂症,这类问题往往还不容易定位,排查费时费力最后发现是一个很 ...
- js中的隐式转换
js中的不同的数据类型之间的比较转换规则如下: 1. 对象和布尔值比较 对象和布尔值进行比较时,对象先转换为字符串,然后再转换为数字,布尔值直接转换为数字 [] == true; //false [] ...
- C++中的explicit关键字 - 抑制隐式转换(转)
在C++程序中很少有人去使用 explicit 关键字,不可否认,在平时的实践中确实很少能用的上.再说C++的功能强大,往往一个问题可以利用好几种C++特性去解决.但稍微留心一下就会发现现有的MFC库 ...
随机推荐
- Linux基础命令---lynx浏览器
lynx lynx是一个字符界面的全功能www浏览器,它没有图形界面,因此占用的资源较少. 此命令的适用范围:RedHat.RHEL.Ubuntu.CentOS.Fedora. 1.语法 ...
- LINUX 安装增强 前置安装文件
yum install kernel yum install kernel-devel yum install gcc yum install make
- Spring Boot对日志的控制
一.logback日志技术介绍 Spring Boot中使用的日志技术为logback.其与Log4J都出自同一人,性能要优于Log4J,是Log4J的替代者. 在Spring Boot中若要使用lo ...
- 【Linux】【Services】【Project】Haproxy Keepalived Postfix实现邮件网关Cluster
1. 简介: 1.1. 背景:公司使用exchange服务器作为邮件服务器,但是使用Postfix作为邮件网关实现病毒检测,内容过滤,反垃圾邮件等功能.原来的架构非常简单,只有两台机器,一个负责进公司 ...
- NSData NSDate NSString NSArray NSDictionary 相互转化
// NSData NSDate NSString NSArray NSDictionary json NSString *string = @"hello word"; ...
- <转>git,github在windows上的搭建
http://www.cnblogs.com/yixiaoyang/archive/2012/01/06/2314190.html Git在源码管理领域目前占很大的比重了,而且开源的项目很多都转到Gi ...
- STM32F103ZET6 核心板制作指引
学点啥系列之 --STM32F103ZET6 核心板制作指引 原创资料,转载请联系 作者的话:会画stm32F103ZET6的话,rct6啥的简直不要太简单 一.电路总览 图1:电路整体 二.单片机部 ...
- 使用Azure Functions & .NET Core快速构建Serverless应用
Code Repo: https://github.com/Asinta/ServerlessApp_NetconfChina2020 Prerequisites Visual Studio Code ...
- 深刨显式锁ReentrantLock原理及其与内置锁的区别,以及读写锁ReentrantReadWriteLock使用场景
13.显示锁 在Java5.0之前,在协调对共享对象的访问时可以使用的机制只有synchronized和volatile.Java5.0增加了一种新的机制:ReentrantLock.与之前提到过的机 ...
- CF363A Soroban 题解
Content 给出一个数 \(n\),请你用算盘来表示 \(n\). 这里的算盘和普通的算盘一样,只不过竖着摆放罢了.左边只有一个珠子,每个珠子表示 \(5\):右边有四个珠子,每个珠子表示 \(1 ...