当前位置: 首页 > news >正文

C# 类型系统

文章目录

  • 1. 隐式类型
      • 隐式类型的数据转换
  • 2. 匿名类型
  • 3. 编译时类型 和 运行时类型
  • 4. 装箱和取消装箱操作

1. 隐式类型

c#允许使用 var 声明变量,编译期会通过初始化语句右侧的表达式推断出变量的类型。

// i is compiled as an int
var i = 5;// s is compiled as a string
var s = "Hello";// a is compiled as int[]
var a = new[] { 0, 1, 2 };// expr is compiled as IEnumerable<Customer>
// or perhaps IQueryable<Customer>
var expr =from c in customerswhere c.City == "London"select c;// anon is compiled as an anonymous type
var anon = new { Name = "Terry", Age = 34 };// list is compiled as List<int>
var list = new List<int>();

使用var来声明变量,我们可以把注意力放在更为重要的使用部分,只需要准确的变量名,既提供正确的语义,剩下的编译期会去操心变量的类型与后面的使用方法是不是匹配。

甚至有的时候,开发者可能会定义错误或者不合适的数据类型,反而不如var方式声明隐式类型。 如 C# :IQueryable & IEnumerable 中:

var q = from c in dbContext.Customers Where c.City == "London" select c;
var finalAnswer = from c in q order by c.Name select c;

上面的 q 编译期会通过表达式,将 q 退段位 IQueryable 类型,从而将 Where 表达式与第二行的 order by 进行表达树组合,在一次sql查询操作里完成。

假如开发者明确声明了 q 的类型,但是声明为 IEnumerable,那么在第一行结束,就会把 Where 查询到的所有数据传到本地,之后再进行排序。

IEnumerable<Customer> q = from c in dbContext.Customers Where c.City == "London" select c;
var finalAnswer = from c in q order by c.Name select c;

隐式类型的数据转换

在使用隐式类型的时候,需要注意的是类型转换的问题。

有些转换是宽化转化(widening conversion),有些则是窄化转换(narrowing conversion),窄化转换会导致精度下降,例如从 long 到 int 的转换。

    public class VarTypeConvert{public static void Run(){IMagicNumberGenerator<double> doubleMNGenerator = new DoubleMNGenerator();IMagicNumberGenerator<float> floatMNGenerator = new FloatMNGenerator();IMagicNumberGenerator<int> intMNGenerator = new IntMNGenerator();var d = doubleMNGenerator.GenerateMagicNumber();var dtotal = 100 * d / 6;Console.WriteLine($"Double magic number: {d}, Total: {dtotal}");var f = floatMNGenerator.GenerateMagicNumber();var ftotal = 100 * f / 6;Console.WriteLine($"Float magic number: {f}, Total: {ftotal}");var i = intMNGenerator.GenerateMagicNumber();var itotal = 100 * i / 6;Console.WriteLine($"Int magic number: {i}, Total: {itotal}");}}-----------------Output--------------------------
Double magic number: 1, Total: 16.666666666666668
Float magic number: 1, Total: 16.666666
Int magic number: 1, Total: 16

都是计算 100 * x / 6,但是由于 x 的类型不同,隐式类型的公式结果大不相同。由于代码采用了隐式类型的局部变量,因此编译期自己推断变量的类型,即根据赋值符号右边的部分做出最佳选择。

即使我们将公式结果定义为 double,由于 MagicNumbe 是int类型,程序仍然会按照整数运算规则进行计算:

var i = intMNGenerator.GenerateMagicNumber();
double dtotal = 100 * i / 6;
Console.WriteLine($"Int magic number: {i}, Total: {dtotal}");
// Output: Int magic number: 1, Total: 16

2. 匿名类型

隐式类型的局部变量是为了支持匿名类型机制而加入 c# 语言的。如下匿名类型:

var v = new { Amount = 108, Message = "Hello" };
Console.WriteLine(v.Amount + v.Message);

我们在使用查询表达式的时候,常会使用匿名类型,从而对每个对象的属性子集处理和返回:

var productQuery =
from prod in products
select new { prod.Color, prod.Price };foreach (var v in productQuery)
{Console.WriteLine("Color={0}, Price={1}", v.Color, v.Price);
}

匿名类型为我们提供了一种将只读属性封装到单个对象,又不需要显示先定义类型的便捷方法。类名由编译期生成,每个属性的类型由编译期推断

匿名类型支持采用 with 表达式形式的非破坏性修改,从而使开发者可以创造匿名类型的新实例。

var p1 = new NamedPoint("A", 0, 0);
Console.WriteLine($"{nameof(p1)}: {p1}");  // output: p1: NamedPoint { Name = A, X = 0, Y = 0 }var p2 = p1 with { Name = "B", X = 5 };
Console.WriteLine($"{nameof(p2)}: {p2}");  // output: p2: NamedPoint { Name = B, X = 5, Y = 0 }     

值得注意的是,匿名类的 EqualsGetHashCode 方法是根据属性的 EqualsGetHashCode 定义的,因此仅当同一匿名类型两个实例所有属性都相等,这两个实例相等。

3. 编译时类型 和 运行时类型

编译时类型即直接声明的类型,或推断类型(c# 隐式类型)。

需要注意的是隐式类型的变量,编译器推断出来的类型不一定是我们期望的类型,比如:

// 推断类型为字符串
var message1 = "This is a string of characters";// 期望类型为 字符的枚举数组
IEnumerable<char> message2 = "This is a string of characters";

运行时类型则是在代码运行阶段实际使用的类型。编译时类型确定编译期执行的所有操作,包括方法调用解析、重载决策以及可用的隐式显示强制转换。运行时类型确定在运行时解析度所有操作,包括调度虚拟方法、计算 is 和 switch 表达式以及其他类型测试API。

object o = Factory.GetObject();
MyType t = o as MyType;
// as 在运行时判断是否是 MyType,如果是则进行类型转换,否则返回 null
if (t != null) ...
else ...

再来看一个编译时类型和运行时类型在类型转换时的例子:

namespace LearnCSharp.Type
{public class MyType{public int data { get; set; }}public class SecondType{private MyType _value;public SecondType(){_value = new MyType(){data = 10};}// 定义类型转换从 SecondType -> MyTypepublic static implicit operator MyType(SecondType st){return st._value;}}public class LTypeFactory{public static SecondType GetObject(){return new SecondType();}}// 测试类型转换public class Program{public static void Main(string[] args){SecondType secondType = new SecondType();// 根据SecondType 中的声明,可以成功转换类型MyType myType = secondType;Console.WriteLine($"Convert SecondType to MyType success: Data = {myType.data}");object o = LTypeFactory.GetObject();MyType t = o as MyType; // 由于运行时类型并不是 MyType,as运算符只会判断运行期类型,不会执行强制类型转换if (t != null){Console.WriteLine($"Convert object to MyType success: Data = {t.data}");}else {Console.WriteLine("Convert object to MyType failed");}try{MyType t1;t1 = (MyType) o;Console.WriteLine($"Convert object to MyType success: Data = {t1.data}");           }catch (Exception ex) // 强制类型转换以对象的编译期类型为依据,所以会认为 o 是 object,而非 SecondType,从而强制转换失败{Console.WriteLine($"Convert object to MyType failed: {ex.Message}");}VarTypeConvert.Run();}}
}--------------------Output----------------------------------
Convert SecondType to MyType success: Data = 10
Convert object to MyType failed
Exception thrown: 'System.InvalidCastException' in LearnCSharp.dll
Convert object to MyType failed: Unable to cast object of type 'LearnCSharpType.SecondType' to type 'LearnCSharp.Type.MyType'.

4. 装箱和取消装箱操作

装箱是将值类型转换为object类型,或由此值类型实现的任何接口类型的过程,新创建的引用对象相当于一个箱子,分配在堆上面,其中含有原值的一份拷贝

取消装箱则是从对象中提取值类型。会把已经装箱的那个值拷贝一份出来。

相对于简单的赋值而言,装箱和取消装箱过程需要大量的计算,装箱时,需要分配构造新对象。取消装箱所需要的强制转换也需要大量计算。

int i = 123;
object o = i;  // explicit boxing

image.png

int i = 123;      // a value type
object o = i;     // boxing
int j = (int)o;   // unboxing

image.png

相关文章:

  • 异步通知驱动实例
  • 数据可视化---使用matplotlib绘制高级图表(2)
  • MySQL—多表查询(概述、基本实操、分类)
  • 240.搜索二维矩阵
  • 开发指南027-微信支付
  • HR招聘面试测评,测评候选人的语言和表达能力
  • 数字化转型中存在的五大问题:意识、供给、成本、能力、竞争力培育
  • Linux命令locate:快速定位文件与目录
  • IO转换流
  • EasyRecovery数据恢复软件具有哪些功能特点?2025版本啥时候更新
  • 大数据学习问题记录
  • 一文读懂筛选控件设计
  • Python深度学习基于Tensorflow(16)基于Tensorflow的对话实例
  • python中有时使用pip安装库而有时又使用conda安装库,到底应该使用哪个管理工具进行库的安装呀?
  • SVG画双色虚线并带有流动效果
  • Angular6错误 Service: No provider for Renderer2
  • Apache Zeppelin在Apache Trafodion上的可视化
  • echarts的各种常用效果展示
  • Java 内存分配及垃圾回收机制初探
  • Nginx 通过 Lua + Redis 实现动态封禁 IP
  • PHP面试之三:MySQL数据库
  • 从0实现一个tiny react(三)生命周期
  • 分布式熔断降级平台aegis
  • 来,膜拜下android roadmap,强大的执行力
  • 码农张的Bug人生 - 见面之礼
  • 爬虫模拟登陆 SegmentFault
  • 前端存储 - localStorage
  • 深度解析利用ES6进行Promise封装总结
  • 用mpvue开发微信小程序
  • 优秀架构师必须掌握的架构思维
  • - 语言经验 - 《c++的高性能内存管理库tcmalloc和jemalloc》
  • 回归生活:清理微信公众号
  • 如何通过报表单元格右键控制报表跳转到不同链接地址 ...
  • ‌移动管家手机智能控制汽车系统
  • # dbt source dbt source freshness命令详解
  • #vue3 实现前端下载excel文件模板功能
  • #微信小程序:微信小程序常见的配置传值
  • (4)logging(日志模块)
  • (WSI分类)WSI分类文献小综述 2024
  • (附源码)计算机毕业设计SSM教师教学质量评价系统
  • (几何:六边形面积)编写程序,提示用户输入六边形的边长,然后显示它的面积。
  • (三) diretfbrc详解
  • (三)docker:Dockerfile构建容器运行jar包
  • (一)插入排序
  • (转)setTimeout 和 setInterval 的区别
  • ./configure,make,make install的作用
  • .cfg\.dat\.mak(持续补充)
  • .net core 控制台应用程序读取配置文件app.config
  • .NET Project Open Day(2011.11.13)
  • .NET Standard 的管理策略
  • .NET3.5下用Lambda简化跨线程访问窗体控件,避免繁复的delegate,Invoke(转)
  • .netcore 如何获取系统中所有session_如何把百度推广中获取的线索(基木鱼,电话,百度商桥等)同步到企业微信或者企业CRM等企业营销系统中...
  • .NET编程C#线程之旅:十种开启线程的方式以及各自使用场景和优缺点
  • .secret勒索病毒数据恢复|金蝶、用友、管家婆、OA、速达、ERP等软件数据库恢复
  • .sh